diff --git a/ChangeLog.md b/ChangeLog.md index f3a4fc16e1488..b71ac99a72a42 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -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 ----------------- diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 4b5220a0c81a8..ba71e1e5b30d7 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -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', @@ -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', diff --git a/src/lib/libsyscall.js b/src/lib/libsyscall.js index c813bf443f2ca..f20a9ae4c614b 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -4,10 +4,6 @@ * SPDX-License-Identifier: MIT */ -{{{ -DEFAULT_POLLMASK = cDefs.POLLIN | cDefs.POLLOUT; -}}} - var SyscallsLibrary = { $SYSCALLS__deps: [ #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM @@ -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 @@ -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 }}}; +#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 diff --git a/system/lib/libc/emscripten_internal.h b/system/lib/libc/emscripten_internal.h index 1f25e1fa549d9..f4c0751acda98 100644 --- a/system/lib/libc/emscripten_internal.h +++ b/system/lib/libc/emscripten_internal.h @@ -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 } diff --git a/system/lib/libc/musl/arch/emscripten/bits/syscall.h b/system/lib/libc/musl/arch/emscripten/bits/syscall.h index 32f84e9fdf491..be60e3d046ffc 100644 --- a/system/lib/libc/musl/arch/emscripten/bits/syscall.h +++ b/system/lib/libc/musl/arch/emscripten/bits/syscall.h @@ -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 diff --git a/system/lib/libc/musl/arch/emscripten/syscall_arch.h b/system/lib/libc/musl/arch/emscripten/syscall_arch.h index 0c0011fd11b08..ff2fb2c559fa2 100644 --- a/system/lib/libc/musl/arch/emscripten/syscall_arch.h +++ b/system/lib/libc/musl/arch/emscripten/syscall_arch.h @@ -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); diff --git a/system/lib/libc/musl/src/select/select.c b/system/lib/libc/musl/src/select/select.c index f2ce026d71808..8b5b533359761 100644 --- a/system/lib/libc/musl/src/select/select.c +++ b/system/lib/libc/musl/src/select/select.c @@ -7,6 +7,12 @@ #define IS32BIT(x) !((x)+0x80000000ULL>>32) #define CLAMP(x) (int)(IS32BIT(x) ? (x) : 0x7fffffffU+((0ULL+(x))>>63)) +#ifdef __EMSCRIPTEN__ +#include +#include +static int emscripten_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *tv); +#endif + int select(int n, fd_set *restrict rfds, fd_set *restrict wfds, fd_set *restrict efds, struct timeval *restrict tv) { time_t s = tv ? tv->tv_sec : 0; @@ -16,8 +22,7 @@ int select(int n, fd_set *restrict rfds, fd_set *restrict wfds, fd_set *restrict if (s<0 || us<0) return __syscall_ret(-EINVAL); #ifdef __EMSCRIPTEN__ - uint64_t millis = tv ? s * 1000 + (us / 1000) : -1; - return syscall_cp(SYS_select, n, rfds, wfds, efds, millis); + return emscripten_select(n, rfds, wfds, efds, tv); #else if (us/1000000 > max_time - s) { s = max_time; @@ -47,3 +52,77 @@ int select(int n, fd_set *restrict rfds, fd_set *restrict wfds, fd_set *restrict #endif #endif } + +#ifdef __EMSCRIPTEN__ +static int emscripten_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *tv) +{ + // Implement select in terms of `poll()` + + // Part 1: convert select arguments into poll arguments + + time_t s = tv ? tv->tv_sec : 0; + suseconds_t us = tv ? tv->tv_usec : 0; + int timeout = tv ? s * 1000 + (us / 1000) : -1; + int n = 0; + struct pollfd* fds = (struct pollfd*)calloc(nfds, sizeof(struct pollfd)); + + for (int i = 0; i < nfds; i++) { + if (readfds && FD_ISSET(i, readfds)) { + fds[n].events |= POLLIN; + } + if (writefds && FD_ISSET(i, writefds)) { + fds[n].events |= POLLOUT; + } + if (exceptfds && FD_ISSET(i, exceptfds)) { + fds[n].events |= POLLPRI; + } + if (fds[n].events) { + fds[n].fd = i; + n++; + } + } + + int rtn = __syscall_poll((intptr_t)fds, n, timeout); + if (rtn < 0) { + free(fds); + return -1; + } + + // Part 2: Translate the result of poll into the results of select(); + + if (readfds) FD_ZERO(readfds); + if (writefds) FD_ZERO(writefds); + if (exceptfds) FD_ZERO(exceptfds); + + int count = 0; + + if (rtn > 0) { + for (int i = 0; i < n; i++) { + int fd = fds[i].fd; + short revents = fds[i].revents; + if (revents) { + // Map POLLIN to readfds + // POLLHUP/POLLERR usually count as readable (EOF or Error) + if (readfds && (revents & POLLIN || revents & POLLHUP || revents & POLLERR)) { + FD_SET(fd, readfds); + count++; + } + // Map POLLOUT to writefds + // POLLERR usually counts as writable (so write fails immediately) + if (writefds && (revents & POLLOUT || revents & POLLERR)) { + FD_SET(fd, writefds); + count++; + } + // Map POLLPRI to exceptfds + if (exceptfds && (revents & POLLPRI)) { + FD_SET(fd, exceptfds); + count++; + } + } + } + } + + free(fds); + return count; +} +#endif diff --git a/system/lib/libc/proxying_poll.c b/system/lib/libc/proxying_poll.c new file mode 100644 index 0000000000000..04e4ee45144e4 --- /dev/null +++ b/system/lib/libc/proxying_poll.c @@ -0,0 +1,55 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include + +#include "emscripten_internal.h" + +#if _REENTRANT +typedef struct proxied_poll_t { + struct pollfd* fds; + int nfds; + int timeout; + int result; +} proxied_poll_t; + +static void call_poll_js(em_proxying_ctx* ctx, void* arg) { + proxied_poll_t* t = arg; + _poll_js(t->fds, t->nfds, t->timeout, ctx, arg); +} + +void _emscripten_proxy_poll_finish(em_proxying_ctx* ctx, void* arg, int ret) { + proxied_poll_t* t = arg; + t->result = ret; + emscripten_proxy_finish(ctx); +} + +static int proxy_poll(struct pollfd *fds, int nfds, int timeout) { + em_proxying_queue* q = emscripten_proxy_get_system_queue(); + pthread_t target = emscripten_main_runtime_thread_id(); + proxied_poll_t t = {.fds = fds, .nfds = nfds, .timeout = timeout}; + if (!emscripten_proxy_sync_with_ctx(q, target, call_poll_js, &t)) { + assert(false && "emscripten_proxy_sync failed"); + return -1; + } + return t.result; +} +#endif + +// Marked as weak since libwasmfs needs to be able to override this. +weak int __syscall_poll(intptr_t _fds, int nfds, int timeout) { + struct pollfd* fds = (struct pollfd*)_fds; +#if _REENTRANT + if (!emscripten_is_main_runtime_thread()) { + return proxy_poll(fds, nfds, timeout); + } +#endif + return _poll_js(fds, nfds, timeout, 0, 0); +} diff --git a/system/lib/libc/proxying_select.c b/system/lib/libc/proxying_select.c deleted file mode 100644 index af3bba01c8beb..0000000000000 --- a/system/lib/libc/proxying_select.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2025 The Emscripten Authors. All rights reserved. - * Emscripten is available under two separate licenses, the MIT license and the - * University of Illinois/NCSA Open Source License. Both these licenses can be - * found in the LICENSE file. - */ - -#include -#include -#include -#include - -#include "emscripten_internal.h" - -#if _REENTRANT -typedef struct proxied_select_t { - int n; - fd_set *rfds; - fd_set *wfds; - fd_set *efds; - int64_t timeout; - int result; -} proxied_select_t; - -static void call_newselect(em_proxying_ctx* ctx, void* arg) { - proxied_select_t* t = arg; - _newselect_js(ctx, arg, t->n, t->rfds, t->wfds, t->efds, t->timeout); -} - -void _emscripten_proxy_newselect_finish(em_proxying_ctx* ctx, void* arg, int ret) { - proxied_select_t* t = arg; - t->result = ret; - emscripten_proxy_finish(ctx); -} - -static int proxy_newselect(int n, fd_set *rfds, fd_set *wfds, fd_set *efds, int64_t timeout) { - em_proxying_queue* q = emscripten_proxy_get_system_queue(); - pthread_t target = emscripten_main_runtime_thread_id(); - proxied_select_t t = {.n = n, .rfds = rfds, .wfds = wfds, .efds = efds, .timeout = timeout}; - if (!emscripten_proxy_sync_with_ctx(q, target, call_newselect, &t)) { - assert(false && "emscripten_proxy_sync failed"); - return -1; - } - return t.result; -} -#endif - -// Marked as weak since libwasmfs needs to be able to override this. -weak int __syscall__newselect(int nfds, intptr_t _readfds, intptr_t _writefds, intptr_t _exceptfds, int64_t timeout) { - fd_set* readfds = (fd_set*)_readfds; - fd_set* writefds = (fd_set*)_writefds; - fd_set* exceptfds = (fd_set*)_exceptfds; -#if _REENTRANT - if (!emscripten_is_main_runtime_thread()) { - return proxy_newselect(nfds, readfds, writefds, exceptfds, timeout); - } -#endif - return _newselect_js(0, 0, nfds, readfds, writefds, exceptfds, timeout); -} diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index e569dfe6e152b..51d76cb652ec8 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -66,7 +66,7 @@ weak int _munmap_js( return -ENOSYS; } -weak int _newselect_js(void* ctx, void* arg, int n, void *rfds, void *wfds, void *efds, int64_t timeout) { +weak int _poll_js(void *fds, int nfds, int timeout, void* ctx, void* arg) { return -ENOSYS; } diff --git a/system/lib/wasmfs/syscalls.cpp b/system/lib/wasmfs/syscalls.cpp index 50c3da75fca05..6e336f4c4fed2 100644 --- a/system/lib/wasmfs/syscalls.cpp +++ b/system/lib/wasmfs/syscalls.cpp @@ -1768,84 +1768,4 @@ int __syscall_fadvise64(int fd, off_t offset, off_t length, int advice) { return 0; } -int __syscall__newselect(int nfds, - intptr_t _readfds, - intptr_t _writefds, - intptr_t _exceptfds, - int64_t timeout) { - // Implement select in terms of `poll()` - - // Part 1: convert select arguments into poll arguments - fd_set* readfds = (fd_set*)_readfds; - fd_set* writefds = (fd_set*)_writefds; - fd_set* exceptfds = (fd_set*)_exceptfds; - - int n = 0; - struct pollfd* fds = (struct pollfd*)calloc(nfds, sizeof(struct pollfd)); - - for (int i = 0; i < nfds; i++) { - if (readfds && FD_ISSET(i, readfds)) { - fds[n].events |= POLLIN; - } - if (writefds && FD_ISSET(i, writefds)) { - fds[n].events |= POLLOUT; - } - if (exceptfds && FD_ISSET(i, exceptfds)) { - fds[n].events |= POLLPRI; - } - if (fds[n].events) { - fds[n].fd = i; - n++; - } - } - - // emscripten's __syscall__newselect already takes a timeout that is - // compatible with __syscall_poll: - // -1: Block forever - // 0: Never block - // >0: Block for up to N milliseconds. - int rtn = __syscall_poll((intptr_t)fds, n, timeout); - if (rtn < 0) { - free(fds); - return -1; - } - - // Part 2: Translate the result of poll into the results of select(); - - if (readfds) FD_ZERO(readfds); - if (writefds) FD_ZERO(writefds); - if (exceptfds) FD_ZERO(exceptfds); - - int count = 0; - - if (rtn > 0) { - for (int i = 0; i < n; i++) { - int fd = fds[i].fd; - short revents = fds[i].revents; - if (revents) { - // Map POLLIN to readfds - // POLLHUP/POLLERR usually count as readable (EOF or Error) - if (readfds && (revents & POLLIN || revents & POLLHUP || revents & POLLERR)) { - FD_SET(fd, readfds); - count++; - } - // Map POLLOUT to writefds - // POLLERR usually counts as writable (so write fails immediately) - if (writefds && (revents & POLLOUT || revents & POLLERR)) { - FD_SET(fd, writefds); - count++; - } - // Map POLLPRI to exceptfds - if (exceptfds && (revents & POLLPRI)) { - FD_SET(fd, exceptfds); - count++; - } - } - } - } - - free(fds); - return count; -} - } // extern "C" diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index c1e0502e9ddac..f7aff3b76c4e2 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 245582, - "a.out.nodebug.wasm": 573702, - "total": 819284, + "a.out.js": 244848, + "a.out.nodebug.wasm": 574227, + "total": 819075, "sent": [ "IMG_Init", "IMG_Load", @@ -248,7 +248,6 @@ "__syscall_newfstatat", "__syscall_openat", "__syscall_pipe", - "__syscall_poll", "__syscall_readlinkat", "__syscall_recvfrom", "__syscall_recvmsg", @@ -288,7 +287,7 @@ "_mmap_js", "_msync_js", "_munmap_js", - "_newselect_js", + "_poll_js", "_setitimer_js", "_timegm_js", "_tzset_js", @@ -1438,7 +1437,6 @@ "env.__syscall_newfstatat", "env.__syscall_openat", "env.__syscall_pipe", - "env.__syscall_poll", "env.__syscall_readlinkat", "env.__syscall_recvfrom", "env.__syscall_recvmsg", @@ -1472,7 +1470,7 @@ "env._mmap_js", "env._msync_js", "env._munmap_js", - "env._newselect_js", + "env._poll_js", "env._setitimer_js", "env._timegm_js", "env._tzset_js", @@ -1863,7 +1861,6 @@ "__subvsi3", "__subvti3", "__synccall", - "__syscall__newselect", "__syscall_acct", "__syscall_getegid32", "__syscall_geteuid32", @@ -1892,6 +1889,7 @@ "__syscall_munmap", "__syscall_pause", "__syscall_pipe2", + "__syscall_poll", "__syscall_prlimit64", "__syscall_pselect6", "__syscall_recvmmsg", @@ -3737,7 +3735,6 @@ "$__subvsi3", "$__subvti3", "$__synccall", - "$__syscall__newselect", "$__syscall_acct", "$__syscall_getgroups32", "$__syscall_getpid", @@ -3754,6 +3751,7 @@ "$__syscall_munmap", "$__syscall_pause", "$__syscall_pipe2", + "$__syscall_poll", "$__syscall_prlimit64", "$__syscall_pselect6", "$__syscall_recvmmsg", diff --git a/test/core/test_poll_blocking.c b/test/core/test_poll_blocking.c new file mode 100644 index 0000000000000..b577f380bbf94 --- /dev/null +++ b/test/core/test_poll_blocking.c @@ -0,0 +1,163 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +// Duplicate of test_select_blocking.c using poll() instead of select() + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Check if timeout works without fds +void test_timeout_without_fds() { + printf("test_timeout_without_fds\n"); + struct timeval begin, end; + + gettimeofday(&begin, NULL); + assert(poll(NULL, 0, 1000) == 0); + gettimeofday(&end, NULL); + assert((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec >= 1000000); +} + +// Check if timeout works with fds without events +void test_timeout_with_fds_without_events() { + printf("test_timeout_with_fds_without_events\n"); + struct timeval begin, end; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + + gettimeofday(&begin, NULL); + struct pollfd fds = {pipe_a[0], 0, 0}; + assert(poll(&fds, 1, 1000) == 0); + gettimeofday(&end, NULL); + assert((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec >= 1000000); + + close(pipe_a[0]); close(pipe_a[1]); +} + +int pipe_shared[2]; + +void *write_after_2s(void * arg) { + const char *t = "test\n"; + + sleep(2); + write(pipe_shared[1], t, strlen(t)); + + return NULL; +} + +// Check if poll can unblock on an event +void test_unblock_poll() { + printf("test_unblock_poll\n"); + struct timeval begin, end; + pthread_t tid; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + assert(pipe(pipe_shared) == 0); + + struct pollfd fds[2] = { + {pipe_a[0], POLLIN, 0}, + {pipe_shared[0], POLLIN, 0}, + }; + assert(pthread_create(&tid, NULL, write_after_2s, NULL) == 0); + gettimeofday(&begin, NULL); + assert(poll(fds, 2, -1) == 1); + gettimeofday(&end, NULL); + assert(fds[1].revents & POLLIN); + assert((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec >= 1000000); + + pthread_join(tid, NULL); + + close(pipe_a[0]); close(pipe_a[1]); + close(pipe_shared[0]); close(pipe_shared[1]); +} + +void *do_poll_in_thread(void * arg) { + struct timeval begin, end; + + gettimeofday(&begin, NULL); + struct pollfd fds = {pipe_shared[0], POLLIN, 0}; + assert(poll(&fds, 1, 4000) == 1); + gettimeofday(&end, NULL); + assert(fds.events & POLLIN); + int duration = (end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec; + assert((duration >= 1000000) && (duration < 4000000)); + + return NULL; +} + +// Check if poll works in threads +void test_poll_in_threads() { + printf("test_poll_in_threads\n"); + pthread_t tid1, tid2; + const char *t = "test\n"; + + assert(pipe(pipe_shared) == 0); + + assert(pthread_create(&tid1, NULL, do_poll_in_thread, NULL) == 0); + assert(pthread_create(&tid2, NULL, do_poll_in_thread, NULL) == 0); + + sleep(2); + write(pipe_shared[1], t, strlen(t)); + + pthread_join(tid1, NULL); + pthread_join(tid2, NULL); + + close(pipe_shared[0]); close(pipe_shared[1]); +} + +// Check if poll works with ready fds +void test_ready_fds() { + printf("test_ready_fds\n"); + struct timeval tv; + fd_set readfds; + const char *t = "test\n"; + int pipe_c[2]; + int pipe_d[2]; + + assert(pipe(pipe_c) == 0); + assert(pipe(pipe_d) == 0); + + write(pipe_c[1], t, strlen(t)); + write(pipe_d[1], t, strlen(t)); + + struct pollfd fds[2] = { + {pipe_c[0], POLLIN, 0}, + {pipe_d[0], POLLIN, 0}, + }; + + assert(poll(fds, 2, 0) == 2); + assert(fds[0].revents & POLLIN); + assert(fds[1].revents & POLLIN); + + fds[0].revents = 0; + fds[1].revents = 0; + + assert(poll(fds, 2, 0) == 2); + assert(fds[0].revents & POLLIN); + assert(fds[1].revents & POLLIN); + + close(pipe_c[0]); close(pipe_c[1]); + close(pipe_d[0]); close(pipe_d[1]); +} + +int main() { + test_poll_in_threads(); + test_timeout_without_fds(); + test_timeout_with_fds_without_events(); + test_unblock_poll(); + test_ready_fds(); + printf("done\n"); + return 0; +} diff --git a/test/core/test_select_blocking.c b/test/core/test_select_blocking.c index f1ec7447ac263..6364b9b40e846 100644 --- a/test/core/test_select_blocking.c +++ b/test/core/test_select_blocking.c @@ -1,3 +1,9 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ #include #include #include @@ -9,6 +15,7 @@ // Check if timeout works without fds void test_timeout_without_fds() { + printf("test_timeout_without_fds\n"); struct timeval tv, begin, end; tv.tv_sec = 1; @@ -21,6 +28,7 @@ void test_timeout_without_fds() { // Check if timeout works with fds without events void test_timeout_with_fds_without_events() { + printf("test_timeout_with_fds_without_events\n"); struct timeval tv, begin, end; fd_set readfds; int pipe_a[2]; @@ -52,9 +60,9 @@ void *write_after_2s(void * arg) { // Check if select can unblock on an event void test_unblock_select() { + printf("test_unblock_select\n"); struct timeval begin, end; fd_set readfds; - int maxfd; pthread_t tid; int pipe_a[2]; @@ -64,7 +72,7 @@ void test_unblock_select() { FD_ZERO(&readfds); FD_SET(pipe_a[0], &readfds); FD_SET(pipe_shared[0], &readfds); - maxfd = (pipe_a[0] > pipe_shared[0] ? pipe_a[0] : pipe_shared[0]); + int maxfd = (pipe_a[0] > pipe_shared[0] ? pipe_a[0] : pipe_shared[0]); assert(pthread_create(&tid, NULL, write_after_2s, NULL) == 0); gettimeofday(&begin, NULL); assert(select(maxfd + 1, &readfds, NULL, NULL, NULL) == 1); @@ -81,21 +89,19 @@ void test_unblock_select() { void *do_select_in_thread(void * arg) { struct timeval begin, end; fd_set readfds; - int maxfd; struct timeval tv; - int duration; tv.tv_sec = 4; tv.tv_usec = 0; FD_ZERO(&readfds); FD_SET(pipe_shared[0], &readfds); - maxfd = pipe_shared[0]; + int maxfd = pipe_shared[0]; gettimeofday(&begin, NULL); assert(select(maxfd + 1, &readfds, NULL, NULL, &tv) == 1); gettimeofday(&end, NULL); assert(FD_ISSET(pipe_shared[0], &readfds)); - duration = (end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec; + int duration = (end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec; assert((duration >= 1000000) && (duration < 4000000)); return NULL; @@ -103,6 +109,7 @@ void *do_select_in_thread(void * arg) { // Check if select works in threads void test_select_in_threads() { + printf("test_select_in_threads\n"); pthread_t tid1, tid2; const char *t = "test\n"; @@ -122,9 +129,9 @@ void test_select_in_threads() { // Check if select works with ready fds void test_ready_fds() { + printf("test_ready_fds\n"); struct timeval tv; fd_set readfds; - int maxfd; const char *t = "test\n"; int pipe_c[2]; int pipe_d[2]; @@ -134,7 +141,7 @@ void test_ready_fds() { write(pipe_c[1], t, strlen(t)); write(pipe_d[1], t, strlen(t)); - maxfd = (pipe_c[0] > pipe_d[0] ? pipe_c[0] : pipe_d[0]); + int maxfd = (pipe_c[0] > pipe_d[0] ? pipe_c[0] : pipe_d[0]); tv.tv_sec = 0; tv.tv_usec = 0; @@ -162,5 +169,6 @@ int main() { test_timeout_with_fds_without_events(); test_unblock_select(); test_ready_fds(); + printf("done\n"); return 0; } diff --git a/test/test_core.py b/test/test_core.py index 316fe1a225aa4..6c50db4ef2d9a 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9669,6 +9669,10 @@ def test_syscall_intercept(self): def test_select_blocking(self): self.do_runf('core/test_select_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + @node_pthreads + def test_poll_blocking(self): + self.do_runf('core/test_poll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + @parameterized({ '': ([],), 'pthread': (['-pthread'],), diff --git a/tools/emscripten.py b/tools/emscripten.py index 4e4b17e847ee6..f440909e3bacd 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -1202,7 +1202,7 @@ def create_pointer_conversion_wrappers(metadata): '_emscripten_dlsync_self_async': '_p', '_emscripten_proxy_dlsync_async': '_pp', '_emscripten_wasm_worker_initialize': '_p_', - '_emscripten_proxy_newselect_finish': '_pp_', + '_emscripten_proxy_poll_finish': '_pp_', '_wasmfs_rename': '_pp', '_wasmfs_readlink': '_pp', '_wasmfs_truncate': '_p_', diff --git a/tools/system_libs.py b/tools/system_libs.py index fef2ec4ff52e3..f6d8183d0a216 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1367,7 +1367,7 @@ def get_files(self): 'sigtimedwait.c', 'wasi-helpers.c', 'system.c', - 'proxying_select.c', + 'proxying_poll.c', ]) if settings.RELOCATABLE or settings.MAIN_MODULE: