From 10a3a109307ff7d6b8d4b1e8d20e8a4149c298ef Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 1 Jun 2026 16:13:35 -0700 Subject: [PATCH] Update __syscall_poll to return -EINTR when it cannot block Previously we were just returning zero in this case (i.e. we were pretending that timeout expired). This required a fix to select() to use __syscall_ret so that it correctly sets errno on failure. Update test_sockets_partial to handle EINTR by returning early. Add a test case to test_pipe_select to verify that select() returns -1 with EINTR on the main thread when no FDs are ready. --- ChangeLog.md | 6 +++- src/lib/libsyscall.js | 7 +++- system/lib/libc/musl/src/select/select.c | 4 +-- test/core/test_pipe_select.c | 38 ++++++++++++++++++++++ test/sockets/test_sockets_partial_client.c | 11 +++++-- test/sockets/test_sockets_partial_server.c | 3 ++ 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 6c0b8e632788e..daa81cd89c2be 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -63,11 +63,15 @@ See docs/process.md for more on how version tagging works. - The `PThread.runningWorkers` field was removed from the `PThread` object. If you have JS code that was depending on this you can transition to using the `PThread.pthreads` object. (#26998) +- The select() and poll() syscalls now fail with EINTR when no FDs are active + and they are asked to block in a build that does not support blocking (i.e. + now JSPI or ASYNCIFY). Previously they would return 0 but without any result + FSs set (i.e. as if the timeout at expired). (#27049) 5.0.7 - 04/30/26 ---------------- - mimalloc was updated to 3.3.1. (#26696) -- The `WASM_JS_TYPES` setting was removed, as the corresponsing propsal was +- The `WASM_JS_TYPES` setting was removed, as the corresponding proposal was pushed back to phase 1. (#26739) - The `-sDETERMINISTIC` setting was removed. This setting just injected `src/deterministic.js` as a `--pre-js`. For now, this file remains part of diff --git a/src/lib/libsyscall.js b/src/lib/libsyscall.js index ed39f4f64d14c..7111916d5b306 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -658,9 +658,14 @@ var SyscallsLibrary = { } #endif + if (!count && timeout) { + // We cannot actually block here since we are not in an async context, + // so return -EINTR, as if we were inturrupted by a signal. #if ASSERTIONS - if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout) + warnOnce('non-zero poll() timeout not supported: ' + timeout) #endif + return -{{{ cDefs.EINTR }}}; + } return count; }, __syscall_getcwd__deps: ['$lengthBytesUTF8', '$stringToUTF8'], diff --git a/system/lib/libc/musl/src/select/select.c b/system/lib/libc/musl/src/select/select.c index 8b5b533359761..60b8852755154 100644 --- a/system/lib/libc/musl/src/select/select.c +++ b/system/lib/libc/musl/src/select/select.c @@ -22,7 +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__ - return emscripten_select(n, rfds, wfds, efds, tv); + return __syscall_ret(emscripten_select(n, rfds, wfds, efds, tv)); #else if (us/1000000 > max_time - s) { s = max_time; @@ -85,7 +85,7 @@ static int emscripten_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set int rtn = __syscall_poll((intptr_t)fds, n, timeout); if (rtn < 0) { free(fds); - return -1; + return rtn; } // Part 2: Translate the result of poll into the results of select(); diff --git a/test/core/test_pipe_select.c b/test/core/test_pipe_select.c index 8a895bfde49af..8a48dd96ddfba 100644 --- a/test/core/test_pipe_select.c +++ b/test/core/test_pipe_select.c @@ -5,6 +5,10 @@ #include #include #include +#include +#ifdef __EMSCRIPTEN__ +#include +#endif int pipe_a[2]; @@ -35,6 +39,40 @@ int main() { assert(get_available(pipe_a[0]) == strlen(t)); assert(get_available(pipe_a[1]) == strlen(t)); + + + // Test select with timeout when no FDs are ready + { + int pipe_b[2]; + assert(pipe(pipe_b) == 0); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(pipe_b[0], &fds); + struct timeval tv = {0, 10000}; // 10ms + +#ifdef __EMSCRIPTEN__ + if (emscripten_is_main_runtime_thread()) { + printf("Main thread: expecting EINTR\n"); + int res = select(pipe_b[0] + 1, &fds, NULL, NULL, &tv); + assert(res == -1); + assert(errno == EINTR); + } else { + printf("Worker thread: expecting timeout\n"); + int res = select(pipe_b[0] + 1, &fds, NULL, NULL, &tv); + assert(res == 0); + } +#else + // Native: should timeout + printf("Native: expecting timeout\n"); + int res = select(pipe_b[0] + 1, &fds, NULL, NULL, &tv); + assert(res == 0); +#endif + + close(pipe_b[0]); + close(pipe_b[1]); + } + close(pipe_a[0]); close(pipe_a[1]); return 0; diff --git a/test/sockets/test_sockets_partial_client.c b/test/sockets/test_sockets_partial_client.c index 0c6ab43847373..5720e1de3953d 100644 --- a/test/sockets/test_sockets_partial_client.c +++ b/test/sockets/test_sockets_partial_client.c @@ -45,9 +45,14 @@ void iter() { FD_SET(sockfd, &fdr); res = select(64, &fdr, NULL, NULL, NULL); if (res == -1) { - perror("select failed"); - finish(EXIT_FAILURE); - } else if (!FD_ISSET(sockfd, &fdr)) { + if (errno != EINTR) { + perror("select failed"); + finish(EXIT_FAILURE); + } + return; + } + + if (!FD_ISSET(sockfd, &fdr)) { return; } diff --git a/test/sockets/test_sockets_partial_server.c b/test/sockets/test_sockets_partial_server.c index 600d3c670e67b..4aec56734bf69 100644 --- a/test/sockets/test_sockets_partial_server.c +++ b/test/sockets/test_sockets_partial_server.c @@ -82,6 +82,9 @@ void iter() { if (clientfd) FD_SET(clientfd, &fdw); res = select(64, &fdr, &fdw, NULL, NULL); if (res == -1) { + if (errno == EINTR) { + return; + } perror("select failed"); exit(EXIT_SUCCESS); }