Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works.

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

4.0.22 - 12/18/25
-----------------
Expand Down
3 changes: 1 addition & 2 deletions src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ sigs = {
__syscall_newfstatat__sig: 'iippi',
__syscall_openat__sig: 'iipip',
__syscall_pipe__sig: 'ip',
__syscall_poll__sig: 'ipii',
__syscall_readlinkat__sig: 'iippp',
__syscall_recvfrom__sig: 'iippipp',
__syscall_recvmsg__sig: 'iipiiii',
Expand Down Expand Up @@ -380,7 +379,7 @@ sigs = {
_mmap_js__sig: 'ipiiijpp',
_msync_js__sig: 'ippiiij',
_munmap_js__sig: 'ippiiij',
_newselect_js__sig: 'ippipppj',
_poll_js__sig: 'ipiipp',
_setitimer_js__sig: 'iid',
_timegm_js__sig: 'jp',
_tzset_js__sig: 'vpppp',
Expand Down
236 changes: 75 additions & 161 deletions src/lib/libsyscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
* SPDX-License-Identifier: MIT
*/

{{{
DEFAULT_POLLMASK = cDefs.POLLIN | cDefs.POLLOUT;
}}}

var SyscallsLibrary = {
$SYSCALLS__deps: [
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
Expand Down Expand Up @@ -107,63 +103,6 @@ var SyscallsLibrary = {
},
},

$parseSelectFDSet__internal: true,
$parseSelectFDSet: (readfds, writefds, exceptfds) => {
var total = 0;

var srcReadLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0),
srcReadHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0);
var srcWriteLow = (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0),
srcWriteHigh = (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0);
var srcExceptLow = (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0),
srcExceptHigh = (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0);

var dstReadLow = 0,
dstReadHigh = 0;
var dstWriteLow = 0,
dstWriteHigh = 0;
var dstExceptLow = 0,
dstExceptHigh = 0;

var check = (fd, low, high, val) => fd < 32 ? (low & val) : (high & val);

return {
allLow: srcReadLow | srcWriteLow | srcExceptLow,
allHigh: srcReadHigh | srcWriteHigh | srcExceptHigh,
getTotal: () => total,
setFlags: (fd, flags) => {
var mask = 1 << (fd % 32);

if ((flags & {{{ cDefs.POLLIN }}}) && check(fd, srcReadLow, srcReadHigh, mask)) {
fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask);
total++;
}
if ((flags & {{{ cDefs.POLLOUT }}}) && check(fd, srcWriteLow, srcWriteHigh, mask)) {
fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask);
total++;
}
if ((flags & {{{ cDefs.POLLPRI }}}) && check(fd, srcExceptLow, srcExceptHigh, mask)) {
fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask);
total++;
}
},
commit: () => {
if (readfds) {
{{{ makeSetValue('readfds', '0', 'dstReadLow', 'i32') }}};
{{{ makeSetValue('readfds', '4', 'dstReadHigh', 'i32') }}};
}
if (writefds) {
{{{ makeSetValue('writefds', '0', 'dstWriteLow', 'i32') }}};
{{{ makeSetValue('writefds', '4', 'dstWriteHigh', 'i32') }}};
}
if (exceptfds) {
{{{ makeSetValue('exceptfds', '0', 'dstExceptLow', 'i32') }}};
{{{ makeSetValue('exceptfds', '4', 'dstExceptHigh', 'i32') }}};
}
}
};
},

$syscallGetVarargI__internal: true,
$syscallGetVarargI: () => {
#if ASSERTIONS
Expand Down Expand Up @@ -602,133 +541,108 @@ var SyscallsLibrary = {
FS.chdir(stream.path);
return 0;
},
_newselect_js__i53abi: true,
_newselect_js__proxy: 'none',
_newselect_js__deps: ['$parseSelectFDSet',
_msync_js__i53abi: true,
_msync_js: (addr, len, prot, flags, fd, offset) => {
if (isNaN(offset)) return -{{{ cDefs.EOVERFLOW }}};
SYSCALLS.doMsync(addr, SYSCALLS.getStreamFromFD(fd), len, flags, offset);
return 0;
},
__syscall_fdatasync: (fd) => {
var stream = SYSCALLS.getStreamFromFD(fd);
return 0; // we can't do anything synchronously; the in-memory FS is already synced to
},
_poll_js__proxy: 'none',
_poll_js__deps: [
#if PTHREADS
'_emscripten_proxy_newselect_finish',
'_emscripten_proxy_poll_finish',
#endif
],
_newselect_js: (ctx, arg, nfds, readfds, writefds, exceptfds, timeoutInMillis) => {
// readfds are supported,
// writefds checks socket open status
// exceptfds are supported, although on web, such exceptional conditions never arise in web sockets
// and so the exceptfds list will always return empty.
// timeout is supported, although on SOCKFS these are ignored and always treated as 0 - fully async
// and PIPEFS supports timeout only when the select is called from a worker.
#if ASSERTIONS
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
_poll_js: (fds, nfds, timeout, ctx, arg) => {
#if PTHREADS
assert(!ENVIRONMENT_IS_PTHREAD, '_newselect_js must be called in the main thread');
// Enable event handlers only when the poll call is proxied from a worker.
var cleanupFuncs = [];
var notifyDone = false;
function asyncPollComplete(count) {
if (notifyDone) {
return;
}
notifyDone = true;
#if RUNTIME_DEBUG
dbg('asyncPollComplete', count);
#endif
cleanupFuncs.forEach(cb => cb());
__emscripten_proxy_poll_finish(ctx, arg, count);
}
function makeNotifyCallback(stream, pollfd) {
var cb = (flags) => {
if (notifyDone) {
return;
}
#if RUNTIME_DEBUG
dbg(`async poll notify: stream=${stream}`);
#endif
var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}};
flags &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
Comment thread
sbc100 marked this conversation as resolved.
#if ASSERTIONS
assert(flags)
#endif

var fdSet = parseSelectFDSet(readfds, writefds, exceptfds);

var allLow = fdSet.allLow;
var allHigh = fdSet.allHigh;

var check = (fd, low, high, val) => fd < 32 ? (low & val) : (high & val);

#if PTHREADS
var makeNotifyCallback = null;
if (ctx) {
// Enable event handlers only when the select call is proxied from a worker.
var cleanupFuncs = [];
var notifyDone = false;
makeNotifyCallback = (fd) => {
var cb = (flags) => {
if (notifyDone) {
return;
}
if (fd >= 0) {
fdSet.setFlags(fd, flags);
}
notifyDone = true;
cleanupFuncs.forEach(cb => cb());
fdSet.commit();
__emscripten_proxy_newselect_finish(ctx, arg, fdSet.getTotal());
}
cb.registerCleanupFunc = (f) => {
if (f != null) cleanupFuncs.push(f);
}
return cb;
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
asyncPollComplete(1);
}
if (timeoutInMillis > 0) {
setTimeout(() => makeNotifyCallback(-1)(0), timeoutInMillis);
cb.registerCleanupFunc = (f) => {
if (f) cleanupFuncs.push(f);
}
return cb;
}
#endif

for (var fd = 0; fd < nfds; fd++) {
var mask = 1 << (fd % 32);
if (!(check(fd, allLow, allHigh, mask))) {
continue; // index isn't in the set
}

var stream = SYSCALLS.getStreamFromFD(fd);

var flags = {{{ DEFAULT_POLLMASK }}};

if (stream.stream_ops.poll) {
flags = (() => {
#if PTHREADS
if (makeNotifyCallback != null) {
return stream.stream_ops.poll(stream, timeoutInMillis, timeoutInMillis != 0 ? makeNotifyCallback(fd) : null);
}
if (ctx) {
#if RUNTIME_DEBUG
dbg('async poll start');
#endif
return stream.stream_ops.poll(stream, timeoutInMillis);
})();
} else {
#if ASSERTIONS
if (timeoutInMillis != 0) warnOnce('non-zero select() timeout not supported: ' + timeoutInMillis)
if (timeout > 0) {
setTimeout(() => {
#if RUNTIME_DEBUG
dbg('poll: timeout');
#endif
asyncPollComplete(0);
}, timeout);
}

fdSet.setFlags(fd, flags);
}

#if PTHREADS
if (makeNotifyCallback != null) {
if ((fdSet.getTotal() > 0) || (timeoutInMillis == 0) ) {
makeNotifyCallback(-1)(0);
}
return 0;
}
#endif

fdSet.commit();

return fdSet.getTotal();
},
_msync_js__i53abi: true,
_msync_js: (addr, len, prot, flags, fd, offset) => {
if (isNaN(offset)) return -{{{ cDefs.EOVERFLOW }}};
SYSCALLS.doMsync(addr, SYSCALLS.getStreamFromFD(fd), len, flags, offset);
return 0;
},
__syscall_fdatasync: (fd) => {
var stream = SYSCALLS.getStreamFromFD(fd);
return 0; // we can't do anything synchronously; the in-memory FS is already synced to
},
__syscall_poll: (fds, nfds, timeout) => {
var count = 0;
for (var i = 0; i < nfds; i++) {
var pollfd = fds + {{{ C_STRUCTS.pollfd.__size__ }}} * i;
var fd = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}};
var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}};
var mask = {{{ cDefs.POLLNVAL }}};
var flags = {{{ cDefs.POLLNVAL }}};
var stream = FS.getStream(fd);
if (stream) {
mask = {{{ DEFAULT_POLLMASK }}};
if (stream.stream_ops.poll) {
mask = stream.stream_ops.poll(stream, -1);
#if PTHREADS
if (ctx && timeout) {
flags = stream.stream_ops.poll(stream, timeout, makeNotifyCallback(stream, pollfd));
} else
#endif
flags = stream.stream_ops.poll(stream, -1);
} else {
flags = {{{ cDefs.POLLIN | cDefs.POLLOUT }}};
}
}
mask &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
if (mask) count++;
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'mask', 'i16') }}};
flags &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
if (flags) count++;
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
}

#if PTHREADS
if (ctx) {
if (count || !timeout) {
asyncPollComplete(count);
}
return 0;
}
#endif

#if ASSERTIONS
if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout)
#endif
Expand Down
2 changes: 1 addition & 1 deletion system/lib/libc/emscripten_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ EmscriptenDeviceOrientationEvent* _emscripten_get_last_deviceorientation_event()
EmscriptenDeviceMotionEvent* _emscripten_get_last_devicemotion_event();
EmscriptenMouseEvent* _emscripten_get_last_mouse_event();

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

#ifdef __cplusplus
}
Expand Down
1 change: 0 additions & 1 deletion system/lib/libc/musl/arch/emscripten/bits/syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#define SYS_mprotect __syscall_mprotect
#define SYS_getpgid __syscall_getpgid
#define SYS_fchdir __syscall_fchdir
#define SYS__newselect __syscall__newselect
#define SYS_msync __syscall_msync
#define SYS_getsid __syscall_getsid
#define SYS_fdatasync __syscall_fdatasync
Expand Down
1 change: 0 additions & 1 deletion system/lib/libc/musl/arch/emscripten/syscall_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ int __syscall_uname(intptr_t buf);
int __syscall_mprotect(size_t addr, size_t len, int prot);
int __syscall_getpgid(int pid);
int __syscall_fchdir(int fd);
int __syscall__newselect(int nfds, intptr_t readfds, intptr_t writefds, intptr_t exceptfds, int64_t timeout);
int __syscall_msync(intptr_t addr, size_t len, int flags);
int __syscall_getsid(int pid);
int __syscall_fdatasync(int fd);
Expand Down
Loading