Skip to content

Commit eb4a4d0

Browse files
authored
Implement only poll() and not select() in JS (#25990)
This change improves the implementation of poll() to support blocking when called from threads (see #25523) and moves the select-based-on-poll implementation from being wasmfs specific to be always being used. Also, add testing for blocking version of poll by duplicating the blocking select test.
1 parent 3456b05 commit eb4a4d0

File tree

17 files changed

+409
-327
lines changed

17 files changed

+409
-327
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.23 (in development)
2222
-----------------------
23+
- The `select()` and `poll()` system calls can now block under certain
24+
circumstances. Specifically, if they are called from a background thread and
25+
file descriptors include pipes. (#25523, #25990)
2326

2427
4.0.22 - 12/18/25
2528
-----------------

src/lib/libsigs.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ sigs = {
262262
__syscall_newfstatat__sig: 'iippi',
263263
__syscall_openat__sig: 'iipip',
264264
__syscall_pipe__sig: 'ip',
265-
__syscall_poll__sig: 'ipii',
266265
__syscall_readlinkat__sig: 'iippp',
267266
__syscall_recvfrom__sig: 'iippipp',
268267
__syscall_recvmsg__sig: 'iipiiii',
@@ -380,7 +379,7 @@ sigs = {
380379
_mmap_js__sig: 'ipiiijpp',
381380
_msync_js__sig: 'ippiiij',
382381
_munmap_js__sig: 'ippiiij',
383-
_newselect_js__sig: 'ippipppj',
382+
_poll_js__sig: 'ipiipp',
384383
_setitimer_js__sig: 'iid',
385384
_timegm_js__sig: 'jp',
386385
_tzset_js__sig: 'vpppp',

src/lib/libsyscall.js

Lines changed: 75 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
* SPDX-License-Identifier: MIT
55
*/
66

7-
{{{
8-
DEFAULT_POLLMASK = cDefs.POLLIN | cDefs.POLLOUT;
9-
}}}
10-
117
var SyscallsLibrary = {
128
$SYSCALLS__deps: [
139
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
@@ -107,63 +103,6 @@ var SyscallsLibrary = {
107103
},
108104
},
109105

110-
$parseSelectFDSet__internal: true,
111-
$parseSelectFDSet: (readfds, writefds, exceptfds) => {
112-
var total = 0;
113-
114-
var srcReadLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0),
115-
srcReadHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0);
116-
var srcWriteLow = (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0),
117-
srcWriteHigh = (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0);
118-
var srcExceptLow = (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0),
119-
srcExceptHigh = (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0);
120-
121-
var dstReadLow = 0,
122-
dstReadHigh = 0;
123-
var dstWriteLow = 0,
124-
dstWriteHigh = 0;
125-
var dstExceptLow = 0,
126-
dstExceptHigh = 0;
127-
128-
var check = (fd, low, high, val) => fd < 32 ? (low & val) : (high & val);
129-
130-
return {
131-
allLow: srcReadLow | srcWriteLow | srcExceptLow,
132-
allHigh: srcReadHigh | srcWriteHigh | srcExceptHigh,
133-
getTotal: () => total,
134-
setFlags: (fd, flags) => {
135-
var mask = 1 << (fd % 32);
136-
137-
if ((flags & {{{ cDefs.POLLIN }}}) && check(fd, srcReadLow, srcReadHigh, mask)) {
138-
fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask);
139-
total++;
140-
}
141-
if ((flags & {{{ cDefs.POLLOUT }}}) && check(fd, srcWriteLow, srcWriteHigh, mask)) {
142-
fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask);
143-
total++;
144-
}
145-
if ((flags & {{{ cDefs.POLLPRI }}}) && check(fd, srcExceptLow, srcExceptHigh, mask)) {
146-
fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask);
147-
total++;
148-
}
149-
},
150-
commit: () => {
151-
if (readfds) {
152-
{{{ makeSetValue('readfds', '0', 'dstReadLow', 'i32') }}};
153-
{{{ makeSetValue('readfds', '4', 'dstReadHigh', 'i32') }}};
154-
}
155-
if (writefds) {
156-
{{{ makeSetValue('writefds', '0', 'dstWriteLow', 'i32') }}};
157-
{{{ makeSetValue('writefds', '4', 'dstWriteHigh', 'i32') }}};
158-
}
159-
if (exceptfds) {
160-
{{{ makeSetValue('exceptfds', '0', 'dstExceptLow', 'i32') }}};
161-
{{{ makeSetValue('exceptfds', '4', 'dstExceptHigh', 'i32') }}};
162-
}
163-
}
164-
};
165-
},
166-
167106
$syscallGetVarargI__internal: true,
168107
$syscallGetVarargI: () => {
169108
#if ASSERTIONS
@@ -602,133 +541,108 @@ var SyscallsLibrary = {
602541
FS.chdir(stream.path);
603542
return 0;
604543
},
605-
_newselect_js__i53abi: true,
606-
_newselect_js__proxy: 'none',
607-
_newselect_js__deps: ['$parseSelectFDSet',
544+
_msync_js__i53abi: true,
545+
_msync_js: (addr, len, prot, flags, fd, offset) => {
546+
if (isNaN(offset)) return -{{{ cDefs.EOVERFLOW }}};
547+
SYSCALLS.doMsync(addr, SYSCALLS.getStreamFromFD(fd), len, flags, offset);
548+
return 0;
549+
},
550+
__syscall_fdatasync: (fd) => {
551+
var stream = SYSCALLS.getStreamFromFD(fd);
552+
return 0; // we can't do anything synchronously; the in-memory FS is already synced to
553+
},
554+
_poll_js__proxy: 'none',
555+
_poll_js__deps: [
608556
#if PTHREADS
609-
'_emscripten_proxy_newselect_finish',
557+
'_emscripten_proxy_poll_finish',
610558
#endif
611559
],
612-
_newselect_js: (ctx, arg, nfds, readfds, writefds, exceptfds, timeoutInMillis) => {
613-
// readfds are supported,
614-
// writefds checks socket open status
615-
// exceptfds are supported, although on web, such exceptional conditions never arise in web sockets
616-
// and so the exceptfds list will always return empty.
617-
// timeout is supported, although on SOCKFS these are ignored and always treated as 0 - fully async
618-
// and PIPEFS supports timeout only when the select is called from a worker.
619-
#if ASSERTIONS
620-
assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits // TODO: this could be 1024 based on current musl headers
560+
_poll_js: (fds, nfds, timeout, ctx, arg) => {
621561
#if PTHREADS
622-
assert(!ENVIRONMENT_IS_PTHREAD, '_newselect_js must be called in the main thread');
562+
// Enable event handlers only when the poll call is proxied from a worker.
563+
var cleanupFuncs = [];
564+
var notifyDone = false;
565+
function asyncPollComplete(count) {
566+
if (notifyDone) {
567+
return;
568+
}
569+
notifyDone = true;
570+
#if RUNTIME_DEBUG
571+
dbg('asyncPollComplete', count);
572+
#endif
573+
cleanupFuncs.forEach(cb => cb());
574+
__emscripten_proxy_poll_finish(ctx, arg, count);
575+
}
576+
function makeNotifyCallback(stream, pollfd) {
577+
var cb = (flags) => {
578+
if (notifyDone) {
579+
return;
580+
}
581+
#if RUNTIME_DEBUG
582+
dbg(`async poll notify: stream=${stream}`);
623583
#endif
584+
var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}};
585+
flags &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
586+
#if ASSERTIONS
587+
assert(flags)
624588
#endif
625-
626-
var fdSet = parseSelectFDSet(readfds, writefds, exceptfds);
627-
628-
var allLow = fdSet.allLow;
629-
var allHigh = fdSet.allHigh;
630-
631-
var check = (fd, low, high, val) => fd < 32 ? (low & val) : (high & val);
632-
633-
#if PTHREADS
634-
var makeNotifyCallback = null;
635-
if (ctx) {
636-
// Enable event handlers only when the select call is proxied from a worker.
637-
var cleanupFuncs = [];
638-
var notifyDone = false;
639-
makeNotifyCallback = (fd) => {
640-
var cb = (flags) => {
641-
if (notifyDone) {
642-
return;
643-
}
644-
if (fd >= 0) {
645-
fdSet.setFlags(fd, flags);
646-
}
647-
notifyDone = true;
648-
cleanupFuncs.forEach(cb => cb());
649-
fdSet.commit();
650-
__emscripten_proxy_newselect_finish(ctx, arg, fdSet.getTotal());
651-
}
652-
cb.registerCleanupFunc = (f) => {
653-
if (f != null) cleanupFuncs.push(f);
654-
}
655-
return cb;
589+
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
590+
asyncPollComplete(1);
656591
}
657-
if (timeoutInMillis > 0) {
658-
setTimeout(() => makeNotifyCallback(-1)(0), timeoutInMillis);
592+
cb.registerCleanupFunc = (f) => {
593+
if (f) cleanupFuncs.push(f);
659594
}
595+
return cb;
660596
}
661-
#endif
662-
663-
for (var fd = 0; fd < nfds; fd++) {
664-
var mask = 1 << (fd % 32);
665-
if (!(check(fd, allLow, allHigh, mask))) {
666-
continue; // index isn't in the set
667-
}
668-
669-
var stream = SYSCALLS.getStreamFromFD(fd);
670-
671-
var flags = {{{ DEFAULT_POLLMASK }}};
672597
673-
if (stream.stream_ops.poll) {
674-
flags = (() => {
675-
#if PTHREADS
676-
if (makeNotifyCallback != null) {
677-
return stream.stream_ops.poll(stream, timeoutInMillis, timeoutInMillis != 0 ? makeNotifyCallback(fd) : null);
678-
}
598+
if (ctx) {
599+
#if RUNTIME_DEBUG
600+
dbg('async poll start');
679601
#endif
680-
return stream.stream_ops.poll(stream, timeoutInMillis);
681-
})();
682-
} else {
683-
#if ASSERTIONS
684-
if (timeoutInMillis != 0) warnOnce('non-zero select() timeout not supported: ' + timeoutInMillis)
602+
if (timeout > 0) {
603+
setTimeout(() => {
604+
#if RUNTIME_DEBUG
605+
dbg('poll: timeout');
685606
#endif
607+
asyncPollComplete(0);
608+
}, timeout);
686609
}
687-
688-
fdSet.setFlags(fd, flags);
689-
}
690-
691-
#if PTHREADS
692-
if (makeNotifyCallback != null) {
693-
if ((fdSet.getTotal() > 0) || (timeoutInMillis == 0) ) {
694-
makeNotifyCallback(-1)(0);
695-
}
696-
return 0;
697610
}
698611
#endif
699612
700-
fdSet.commit();
701-
702-
return fdSet.getTotal();
703-
},
704-
_msync_js__i53abi: true,
705-
_msync_js: (addr, len, prot, flags, fd, offset) => {
706-
if (isNaN(offset)) return -{{{ cDefs.EOVERFLOW }}};
707-
SYSCALLS.doMsync(addr, SYSCALLS.getStreamFromFD(fd), len, flags, offset);
708-
return 0;
709-
},
710-
__syscall_fdatasync: (fd) => {
711-
var stream = SYSCALLS.getStreamFromFD(fd);
712-
return 0; // we can't do anything synchronously; the in-memory FS is already synced to
713-
},
714-
__syscall_poll: (fds, nfds, timeout) => {
715613
var count = 0;
716614
for (var i = 0; i < nfds; i++) {
717615
var pollfd = fds + {{{ C_STRUCTS.pollfd.__size__ }}} * i;
718616
var fd = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}};
719617
var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}};
720-
var mask = {{{ cDefs.POLLNVAL }}};
618+
var flags = {{{ cDefs.POLLNVAL }}};
721619
var stream = FS.getStream(fd);
722620
if (stream) {
723-
mask = {{{ DEFAULT_POLLMASK }}};
724621
if (stream.stream_ops.poll) {
725-
mask = stream.stream_ops.poll(stream, -1);
622+
#if PTHREADS
623+
if (ctx && timeout) {
624+
flags = stream.stream_ops.poll(stream, timeout, makeNotifyCallback(stream, pollfd));
625+
} else
626+
#endif
627+
flags = stream.stream_ops.poll(stream, -1);
628+
} else {
629+
flags = {{{ cDefs.POLLIN | cDefs.POLLOUT }}};
726630
}
727631
}
728-
mask &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
729-
if (mask) count++;
730-
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'mask', 'i16') }}};
632+
flags &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
633+
if (flags) count++;
634+
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
731635
}
636+
637+
#if PTHREADS
638+
if (ctx) {
639+
if (count || !timeout) {
640+
asyncPollComplete(count);
641+
}
642+
return 0;
643+
}
644+
#endif
645+
732646
#if ASSERTIONS
733647
if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout)
734648
#endif

system/lib/libc/emscripten_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ EmscriptenDeviceOrientationEvent* _emscripten_get_last_deviceorientation_event()
151151
EmscriptenDeviceMotionEvent* _emscripten_get_last_devicemotion_event();
152152
EmscriptenMouseEvent* _emscripten_get_last_mouse_event();
153153

154-
int _newselect_js(void* ctx, void* arg, int n, void *rfds, void *wfds, void *efds, int64_t timeout);
154+
int _poll_js(void* fds, int nfds, int timeout, void* ctx, void* arg);
155155

156156
#ifdef __cplusplus
157157
}

system/lib/libc/musl/arch/emscripten/bits/syscall.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
#define SYS_mprotect __syscall_mprotect
2424
#define SYS_getpgid __syscall_getpgid
2525
#define SYS_fchdir __syscall_fchdir
26-
#define SYS__newselect __syscall__newselect
2726
#define SYS_msync __syscall_msync
2827
#define SYS_getsid __syscall_getsid
2928
#define SYS_fdatasync __syscall_fdatasync

system/lib/libc/musl/arch/emscripten/syscall_arch.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ int __syscall_uname(intptr_t buf);
4141
int __syscall_mprotect(size_t addr, size_t len, int prot);
4242
int __syscall_getpgid(int pid);
4343
int __syscall_fchdir(int fd);
44-
int __syscall__newselect(int nfds, intptr_t readfds, intptr_t writefds, intptr_t exceptfds, int64_t timeout);
4544
int __syscall_msync(intptr_t addr, size_t len, int flags);
4645
int __syscall_getsid(int pid);
4746
int __syscall_fdatasync(int fd);

0 commit comments

Comments
 (0)