diff --git a/src/lib/libpipefs.js b/src/lib/libpipefs.js index d9a3fed62180e..f8b55038c8f09 100644 --- a/src/lib/libpipefs.js +++ b/src/lib/libpipefs.js @@ -21,6 +21,23 @@ addToLibrary({ // able to read from the read end after write end is closed. refcnt : 2, timestamp: new Date(), +#if PTHREADS + readableHandlers: [], + registerReadableHandler: (callback) => { + callback.registerCleanupFunc(() => { + const i = pipe.readableHandlers.indexOf(callback); + if (i !== -1) pipe.readableHandlers.splice(i, 1); + }); + pipe.readableHandlers.push(callback); + }, + notifyReadableHandlers: () => { + while (pipe.readableHandlers.length > 0) { + const cb = pipe.readableHandlers.shift(); + if (cb) cb({{{ cDefs.POLLRDNORM }}} | {{{ cDefs.POLLIN }}}); + } + pipe.readableHandlers = []; + } +#endif }; pipe.buckets.push({ @@ -80,7 +97,7 @@ addToLibrary({ blocks: 0, }; }, - poll(stream) { + poll(stream, timeout, notifyCallback) { var pipe = stream.node.pipe; if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_WRONLY }}}) { @@ -92,6 +109,9 @@ addToLibrary({ } } +#if PTHREADS + if (notifyCallback) pipe.registerReadableHandler(notifyCallback); +#endif return 0; }, dup(stream) { @@ -204,6 +224,9 @@ addToLibrary({ if (freeBytesInCurrBuffer >= dataLen) { currBucket.buffer.set(data, currBucket.offset); currBucket.offset += dataLen; +#if PTHREADS + pipe.notifyReadableHandlers(); +#endif return dataLen; } else if (freeBytesInCurrBuffer > 0) { currBucket.buffer.set(data.subarray(0, freeBytesInCurrBuffer), currBucket.offset); @@ -235,6 +258,9 @@ addToLibrary({ newBucket.buffer.set(data); } +#if PTHREADS + pipe.notifyReadableHandlers(); +#endif return dataLen; }, close(stream) { diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index b52e68d131668..4d875a1bb9c42 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -381,6 +381,7 @@ sigs = { _mmap_js__sig: 'ipiiijpp', _msync_js__sig: 'ippiiij', _munmap_js__sig: 'ippiiij', + _newselect_js__sig: 'ippipppj', _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 773d4c76300fe..3624805944ff8 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -104,6 +104,63 @@ 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 @@ -543,37 +600,88 @@ var SyscallsLibrary = { return 0; }, __syscall__newselect__i53abi: true, + __syscall__newselect__proxy: 'none', + __syscall__newselect__deps: ['_newselect_js', +#if PTHREADS + '_emscripten_proxy_newselect', +#endif + ], __syscall__newselect: (nfds, readfds, writefds, exceptfds, timeoutInMillis) => { +#if PTHREADS + if (ENVIRONMENT_IS_PTHREAD) { + return __emscripten_proxy_newselect(nfds, + {{{ to64('readfds') }}}, + {{{ to64('writefds') }}}, + {{{ to64('exceptfds') }}}, + {{{ splitI64('timeoutInMillis') }}}); + } +#endif + return __newselect_js({{{ to64('0') }}}, + {{{ to64('0') }}}, + nfds, + {{{ to64('readfds') }}}, + {{{ to64('writefds') }}}, + {{{ to64('exceptfds') }}}, + {{{ splitI64('timeoutInMillis') }}}); + }, + _newselect_js__i53abi: true, + _newselect_js__proxy: 'none', + _newselect_js__deps: ['$parseSelectFDSet', +#if PTHREADS + '_emscripten_proxy_newselect_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 and PIPEFS these are ignored and always treated as 0 - fully async + // 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 +#if PTHREADS + assert(!ENVIRONMENT_IS_PTHREAD, '_newselect_js must be called in the main thread'); +#endif #endif - 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 fdSet = parseSelectFDSet(readfds, writefds, exceptfds); - var allLow = srcReadLow | srcWriteLow | srcExceptLow; - var allHigh = srcReadHigh | srcWriteHigh | srcExceptHigh; + 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({{{ to64('ctx') }}}, {{{ to64('arg') }}}, fdSet.getTotal()); + } + cb.registerCleanupFunc = (f) => { + if (f != null) cleanupFuncs.push(f); + } + return cb; + } + if (timeoutInMillis > 0) { + setTimeout(() => makeNotifyCallback(-1)(0), timeoutInMillis); + } + } +#endif + for (var fd = 0; fd < nfds; fd++) { var mask = 1 << (fd % 32); if (!(check(fd, allLow, allHigh, mask))) { @@ -585,41 +693,35 @@ var SyscallsLibrary = { var flags = SYSCALLS.DEFAULT_POLLMASK; if (stream.stream_ops.poll) { - flags = stream.stream_ops.poll(stream, timeoutInMillis); + flags = (() => { +#if PTHREADS + if (makeNotifyCallback != null) { + return stream.stream_ops.poll(stream, timeoutInMillis, timeoutInMillis != 0 ? makeNotifyCallback(fd) : null); + } +#endif + return stream.stream_ops.poll(stream, timeoutInMillis); + })(); } else { #if ASSERTIONS if (timeoutInMillis != 0) warnOnce('non-zero select() timeout not supported: ' + timeoutInMillis) #endif } - 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++; - } + fdSet.setFlags(fd, flags); } - 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') }}}; +#if PTHREADS + if (makeNotifyCallback != null) { + if ((fdSet.getTotal() > 0) || (timeoutInMillis == 0) ) { + makeNotifyCallback(-1)(0); + } + return 0; } +#endif + + fdSet.commit(); - return total; + return fdSet.getTotal(); }, _msync_js__i53abi: true, _msync_js: (addr, len, prot, flags, fd, offset) => { diff --git a/system/lib/libc/emscripten_internal.h b/system/lib/libc/emscripten_internal.h index 599f98ad445f8..1f25e1fa549d9 100644 --- a/system/lib/libc/emscripten_internal.h +++ b/system/lib/libc/emscripten_internal.h @@ -151,6 +151,8 @@ 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); + #ifdef __cplusplus } #endif diff --git a/system/lib/libc/proxying_select.c b/system/lib/libc/proxying_select.c new file mode 100644 index 0000000000000..3a42e6de4341d --- /dev/null +++ b/system/lib/libc/proxying_select.c @@ -0,0 +1,43 @@ +/* + * 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 "emscripten_internal.h" + +typedef struct proxied_select_t { + int n; + void *rfds; + void *wfds; + void *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); +} + +int _emscripten_proxy_newselect(int n, void *rfds, void *wfds, void *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; +} diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index fdca8d949fa3f..e569dfe6e152b 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -66,6 +66,10 @@ 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) { + return -ENOSYS; +} + // open(), etc. - we just support the standard streams, with no // corner case error checking; everything else is not permitted. // TODO: full file support for WASI, or an option for it diff --git a/system/lib/wasmfs/syscalls.cpp b/system/lib/wasmfs/syscalls.cpp index 50c3da75fca05..5b907d9f1f94d 100644 --- a/system/lib/wasmfs/syscalls.cpp +++ b/system/lib/wasmfs/syscalls.cpp @@ -1848,4 +1848,8 @@ int __syscall__newselect(int nfds, return count; } +int _newselect_js(void* ctx, void* arg, int n, void *rfds, void *wfds, void *efds, int64_t timeout) { + return -ENOSYS; +} + } // extern "C" diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 4888c32544ba0..6349f9f17bb94 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": 245482, - "a.out.nodebug.wasm": 573657, - "total": 819139, + "a.out.js": 245765, + "a.out.nodebug.wasm": 573911, + "total": 819676, "sent": [ "IMG_Init", "IMG_Load", @@ -289,6 +289,7 @@ "_mmap_js", "_msync_js", "_munmap_js", + "_newselect_js", "_setitimer_js", "_timegm_js", "_tzset_js", @@ -1473,6 +1474,7 @@ "env._mmap_js", "env._msync_js", "env._munmap_js", + "env._newselect_js", "env._setitimer_js", "env._timegm_js", "env._tzset_js", @@ -1961,6 +1963,8 @@ "_emscripten_find_dylib", "_emscripten_memcpy_bulkmem", "_emscripten_memset_bulkmem", + "_emscripten_proxy_newselect", + "_emscripten_proxy_newselect_finish", "_emscripten_run_callback_on_thread", "_emscripten_set_offscreencanvas_size_on_thread", "_emscripten_stack_alloc", @@ -3822,6 +3826,8 @@ "$_emscripten_find_dylib", "$_emscripten_memcpy_bulkmem", "$_emscripten_memset_bulkmem", + "$_emscripten_proxy_newselect", + "$_emscripten_proxy_newselect_finish", "$_emscripten_run_callback_on_thread", "$_emscripten_set_offscreencanvas_size_on_thread", "$_emscripten_stack_alloc", @@ -3905,6 +3911,7 @@ "$cacoshl", "$cacosl", "$call", + "$call_newselect", "$call_once", "$carg", "$cargf", diff --git a/test/core/test_pipe_select.c b/test/core/test_pipe_select.c new file mode 100644 index 0000000000000..54cfaa5417bfb --- /dev/null +++ b/test/core/test_pipe_select.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include +#include + +int pipe_a[2]; + +int main() { + fd_set readfds; + const char *t = "test\n"; + + assert(pipe(pipe_a) == 0); + FD_ZERO(&readfds); + FD_SET(pipe_a[0], &readfds); + write(pipe_a[1], t, strlen(t)); + assert(select(pipe_a[0] + 1, &readfds, NULL, NULL, NULL) == 1); + assert(FD_ISSET(pipe_a[0], &readfds)); + + close(pipe_a[0]); close(pipe_a[1]); + + return 0; +} diff --git a/test/core/test_select_blocking.c b/test/core/test_select_blocking.c new file mode 100644 index 0000000000000..f1ec7447ac263 --- /dev/null +++ b/test/core/test_select_blocking.c @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Check if timeout works without fds +void test_timeout_without_fds() { + struct timeval tv, begin, end; + + tv.tv_sec = 1; + tv.tv_usec = 0; + gettimeofday(&begin, NULL); + assert(select(0, NULL, NULL, NULL, &tv) == 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() { + struct timeval tv, begin, end; + fd_set readfds; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + + tv.tv_sec = 1; + tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(pipe_a[0], &readfds); + gettimeofday(&begin, NULL); + assert(select(pipe_a[0] + 1, &readfds, NULL, NULL, &tv) == 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 select can unblock on an event +void test_unblock_select() { + struct timeval begin, end; + fd_set readfds; + int maxfd; + pthread_t tid; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + assert(pipe(pipe_shared) == 0); + + 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]); + assert(pthread_create(&tid, NULL, write_after_2s, NULL) == 0); + gettimeofday(&begin, NULL); + assert(select(maxfd + 1, &readfds, NULL, NULL, NULL) == 1); + gettimeofday(&end, NULL); + assert(FD_ISSET(pipe_shared[0], &readfds)); + 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_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]; + + 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; + assert((duration >= 1000000) && (duration < 4000000)); + + return NULL; +} + +// Check if select works in threads +void test_select_in_threads() { + pthread_t tid1, tid2; + const char *t = "test\n"; + + assert(pipe(pipe_shared) == 0); + + assert(pthread_create(&tid1, NULL, do_select_in_thread, NULL) == 0); + assert(pthread_create(&tid2, NULL, do_select_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 select works with ready fds +void test_ready_fds() { + struct timeval tv; + fd_set readfds; + int maxfd; + 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)); + maxfd = (pipe_c[0] > pipe_d[0] ? pipe_c[0] : pipe_d[0]); + + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(pipe_c[0], &readfds); + FD_SET(pipe_d[0], &readfds); + assert(select(maxfd + 1, &readfds, NULL, NULL, &tv) == 2); + assert(FD_ISSET(pipe_c[0], &readfds)); + assert(FD_ISSET(pipe_d[0], &readfds)); + + FD_ZERO(&readfds); + FD_SET(pipe_c[0], &readfds); + FD_SET(pipe_d[0], &readfds); + assert(select(maxfd + 1, &readfds, NULL, NULL, NULL) == 2); + assert(FD_ISSET(pipe_c[0], &readfds)); + assert(FD_ISSET(pipe_d[0], &readfds)); + + close(pipe_c[0]); close(pipe_c[1]); + close(pipe_d[0]); close(pipe_d[1]); +} + +int main() { + test_select_in_threads(); + test_timeout_without_fds(); + test_timeout_with_fds_without_events(); + test_unblock_select(); + test_ready_fds(); + return 0; +} diff --git a/test/sockets/test_sockets_echo_client.c b/test/sockets/test_sockets_echo_client.c index 64df6b969b8cc..626e9686d6564 100644 --- a/test/sockets/test_sockets_echo_client.c +++ b/test/sockets/test_sockets_echo_client.c @@ -67,13 +67,15 @@ void main_loop() { fd_set fdr; fd_set fdw; int res; + // Timeout of zero means don't block. Emscripten doesn't support blocking select in the general case + struct timeval tv = {0}; // make sure that server.fd is ready to read / write FD_ZERO(&fdr); FD_ZERO(&fdw); FD_SET(server.fd, &fdr); FD_SET(server.fd, &fdw); - res = select(64, &fdr, &fdw, NULL, NULL); + res = select(64, &fdr, &fdw, NULL, &tv); if (res == -1) { perror("select failed"); finish(EXIT_FAILURE); diff --git a/test/sockets/test_sockets_echo_server.c b/test/sockets/test_sockets_echo_server.c index a77c599e3c310..ca2be0aa3b07a 100644 --- a/test/sockets/test_sockets_echo_server.c +++ b/test/sockets/test_sockets_echo_server.c @@ -77,6 +77,8 @@ void main_loop() { int res; fd_set fdr; fd_set fdw; + // Timeout of zero means don't block. Emscripten doesn't support blocking select in the general case + struct timeval tv = {0}; // see if there are any connections to accept or read / write from FD_ZERO(&fdr); @@ -87,7 +89,7 @@ void main_loop() { if (client.fd) FD_SET(client.fd, &fdr); if (client.fd) FD_SET(client.fd, &fdw); #endif - res = select(64, &fdr, &fdw, NULL, NULL); + res = select(64, &fdr, &fdw, NULL, &tv); if (res == -1) { perror("select failed"); exit(EXIT_SUCCESS); diff --git a/test/test_core.py b/test/test_core.py index 86cef7c353df4..72586881b214c 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9665,6 +9665,16 @@ def test_wasm_global(self, dynlink): def test_syscall_intercept(self): self.do_core_test('test_syscall_intercept.c') + def test_select_blocking(self): + self.do_runf('core/test_select_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + + @parameterized({ + '': ([],), + 'pthread': (['-pthread'],), + }) + def test_pipe_select(self, args): + self.do_runf('core/test_pipe_select.c', cflags=args) + @also_without_bigint def test_jslib_i64_params(self): # Tests the defineI64Param and receiveI64ParamAsI53 helpers that are diff --git a/tools/emscripten.py b/tools/emscripten.py index 58eb91617363d..72b8a35e19dc3 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -1202,6 +1202,8 @@ def create_pointer_conversion_wrappers(metadata): '_emscripten_dlsync_self_async': '_p', '_emscripten_proxy_dlsync_async': '_pp', '_emscripten_wasm_worker_initialize': '_p_', + '_emscripten_proxy_newselect': '__ppp_', + '_emscripten_proxy_newselect_finish': '_pp_', '_wasmfs_rename': '_pp', '_wasmfs_readlink': '_pp', '_wasmfs_truncate': '_p_', diff --git a/tools/system_libs.py b/tools/system_libs.py index b6fde4e9b860e..fef2ec4ff52e3 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1367,6 +1367,7 @@ def get_files(self): 'sigtimedwait.c', 'wasi-helpers.c', 'system.c', + 'proxying_select.c', ]) if settings.RELOCATABLE or settings.MAIN_MODULE: