diff --git a/ChangeLog.md b/ChangeLog.md index ec027ce631917..b1829f4cd3402 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -33,6 +33,10 @@ See docs/process.md for more on how version tagging works. out to `wasm-bindgen` in the users's path and integrate the wasm-bindgen JS with the normal Emscripten JS. Some wasm-bindgen features may not yet be fully supported. (#23493) +- 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) 6.0.0 - 06/04/26 ---------------- @@ -84,7 +88,7 @@ See docs/process.md for more on how version tagging works. 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 e9197aec48bae..2cbc6527a3f39 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -634,9 +634,15 @@ var SyscallsLibrary = { #endif var count = doPoll(fds, nfds, 0, undefined); + 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; }, // The shared readiness derivation: one pass over the pollfds, writing 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/system/lib/wasmfs/syscalls.cpp b/system/lib/wasmfs/syscalls.cpp index 95babec3cec1f..54e9452e4cbed 100644 --- a/system/lib/wasmfs/syscalls.cpp +++ b/system/lib/wasmfs/syscalls.cpp @@ -1391,7 +1391,7 @@ int __syscall_poll(intptr_t fds_, int nfds, int timeout) { // Process the list of FDs and compute their revents masks. Count the number // of nonzero such masks, which is our return value. - int nonzero = 0; + int count = 0; for (nfds_t i = 0; i < nfds; i++) { auto* pollfd = &fds[i]; auto fd = pollfd->fd; @@ -1426,14 +1426,17 @@ int __syscall_poll(intptr_t fds_, int nfds, int timeout) { // TODO: set the state based on the state of the other end of the pipe, for // pipes (POLLERR | POLLHUP) if (mask) { - nonzero++; + count++; } pollfd->revents = mask; } // TODO: This should block based on the timeout. The old FS did not do so due // to web limitations, which we should perhaps revisit (especially with // pthreads and asyncify). - return nonzero; + if (timeout && !count) { + return -EINTR; + } + return count; } // libc routes zero-timeout poll() calls here (see musl's poll.c). WasmFS's diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 584a099573a66..91f42de89dd32 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": 268274, - "a.out.nodebug.wasm": 587640, - "total": 855914, + "a.out.js": 268293, + "a.out.nodebug.wasm": 587659, + "total": 855952, "sent": [ "IMG_Init", "IMG_Load", 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/sdl2_net_server.c b/test/sockets/sdl2_net_server.c index 9aa725ad01bf1..fffe2caab8b85 100644 --- a/test/sockets/sdl2_net_server.c +++ b/test/sockets/sdl2_net_server.c @@ -62,7 +62,7 @@ void main_loop() { /* Check the sd if there is a pending connection. * If there is one, accept that, and open a new socket for communicating */ - SDLNet_CheckSockets(state.socketSet, 20); + SDLNet_CheckSockets(state.socketSet, 0); int serverSocketActivity = SDLNet_SocketReady(state.sd); if (serverSocketActivity) 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); } diff --git a/test/sockets/test_sockets_select_server_closes_connection_client_rw.c b/test/sockets/test_sockets_select_server_closes_connection_client_rw.c index 16195657f56df..8e4e9420db9f9 100644 --- a/test/sockets/test_sockets_select_server_closes_connection_client_rw.c +++ b/test/sockets/test_sockets_select_server_closes_connection_client_rw.c @@ -44,6 +44,7 @@ void main_loop() { int selectRes; ssize_t transferAmount; fd_set sett; + struct timeval zero_timeout = {0, 0}; switch (state) { case 0: @@ -53,7 +54,7 @@ void main_loop() { // select should tell us 0 handles are ready FD_ZERO(&sett); FD_SET(sockfd, &sett); - selectRes = select(64, &sett, NULL, NULL, NULL); + selectRes = select(64, &sett, NULL, NULL, &zero_timeout); if (selectRes != 0) { printf("case 0: read select != 0 (%d)\n", selectRes); finish(EXIT_FAILURE); @@ -63,7 +64,7 @@ void main_loop() { // the connection either is setting up or is established and writing is possible FD_ZERO(&sett); FD_SET(sockfd, &sett); - selectRes = select(64, NULL, &sett, NULL, NULL); + selectRes = select(64, NULL, &sett, NULL, &zero_timeout); if (selectRes == -1) { printf("case 0: write select == -1\n"); finish(EXIT_FAILURE); @@ -86,7 +87,7 @@ void main_loop() { // has sent the data and then closed the connection FD_ZERO(&sett); FD_SET(sockfd, &sett); - selectRes = select(64, &sett, NULL, NULL, NULL); + selectRes = select(64, &sett, NULL, NULL, &zero_timeout); if (selectRes == -1) { printf("case 1: read selectRes == -1\n"); finish(EXIT_FAILURE); @@ -114,7 +115,7 @@ void main_loop() { // succeed, but the socket should not set in the set. FD_ZERO(&sett); FD_SET(sockfd, &sett); - selectRes = select(64, NULL, &sett, NULL, NULL); + selectRes = select(64, NULL, &sett, NULL, &zero_timeout); if (selectRes != 0 || FD_ISSET(sockfd, &sett)) { printf("case 2: write selectRes != 0 || FD_ISSET(sockfd, &sett)\n"); finish(EXIT_FAILURE); @@ -124,7 +125,7 @@ void main_loop() { // has to succeed because there is still data in the inQueue FD_ZERO(&sett); FD_SET(sockfd, &sett); - selectRes = select(64, &sett, NULL, NULL, NULL); + selectRes = select(64, &sett, NULL, NULL, &zero_timeout); if (selectRes != 1) { printf("case 2: read selectRes != 1\n"); finish(EXIT_FAILURE); @@ -152,7 +153,7 @@ void main_loop() { // should succeed FD_ZERO(&sett); FD_SET(sockfd, &sett); - selectRes = select(64, &sett, NULL, NULL, NULL); + selectRes = select(64, &sett, NULL, NULL, &zero_timeout); if (selectRes != 1) { printf("case 3: read selectRes != 1\n"); finish(EXIT_FAILURE); diff --git a/test/unistd/misc.out b/test/unistd/misc.out index 0fa3ae2deeff9..4ed4b37a3b5fd 100644 --- a/test/unistd/misc.out +++ b/test/unistd/misc.out @@ -10,7 +10,7 @@ link: -1, errno: 34 lockf(good): 0, errno: 0 lockf(bad): -1, errno: 8 nice: -1, errno: 63 -pause: 0, errno: 0 +pause: -1, errno: 27 pipe(good): 0, errno: 0 pipe(bad): -1, errno: 21 pipe2(good): 0, errno: 0