From 1a9524a8d238cb2d45475cdb495dd9138d701459 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 19 Aug 2026 20:39:33 -0700 Subject: [PATCH 1/5] Support pthread blocking socket ops via a single fd_wait primitive Blocking accept/recv on sockets, replacing the previous approach of marking the socket data syscalls __async (which, under JSPI, wrapped every one of them in WebAssembly.Suspending - taxing every nonblocking call with a suspend/resume round-trip, since a Suspending import always resolves through a Promise in V8). The data syscalls stay strictly synchronous imports: single attempt, -EAGAIN when they would block. Blocking is factored into one new import, _emscripten_fd_wait(fd, events), and retry loops in the musl wrappers (accept, accept4, recvfrom, recvmsg): on EAGAIN with a blocking fd and no MSG_DONTWAIT, wait for readiness on the inode's listener queue and retry. This is a pthreads-only facility. The retry loops compile only into the -mt libc (gated on __EMSCRIPTEN_PTHREADS__ - the only compile-time boundary libc has; ASYNCIFY is a link-time transform with no libc variant), and _emscripten_fd_wait blocks only on a proxied pthread worker: __proxy sync + __async gives the PROXY_SYNC_ASYNC call path, whose sync-proxy completes - ending the worker's futex wait - when the returned Promise resolves. In every other context, including the event-loop thread which cannot block, it fails with -EAGAIN. Single-threaded ASYNCIFY/JSPI builds use epoll for readiness instead, so a purely-synchronous build keeps the direct doReadv/doWritev path byte-for-byte and hello-world code size is unchanged. accept4 now applies SOCK_NONBLOCK to the accepted fd (on top of the flags it inherits from the listener); without this a SOCK_NONBLOCK accept off a blocking listener wrongly yielded a blocking socket. Send/write paths are untouched: the node backend buffers and never would-blocks, so blocking send degenerates to synchronous buffered success and needs no wait machinery. read()/write() on a socket fd are likewise not covered - only the socket calls themselves. Tested with test_noderawsockets_tcp_blocking (blocking accept + recv that must suspend, under PROXY_TO_PTHREAD) and test_noderawsockets_tcp_accept_nonblock (accept4 SOCK_NONBLOCK off a blocking listener), plus the mio test suite under PROXY_TO_PTHREAD + NODERAWSOCKETS + NODERAWFS: 144 passed, 0 failed, 5 ignored. --- ChangeLog.md | 6 ++ src/lib/libsigs.js | 1 + src/lib/libsyscall.js | 44 +++++++++ .../musl/src/internal/emscripten_fd_wait.h | 42 ++++++++ system/lib/libc/musl/src/network/accept.c | 8 ++ system/lib/libc/musl/src/network/accept4.c | 8 ++ system/lib/libc/musl/src/network/recvfrom.c | 8 ++ system/lib/libc/musl/src/network/recvmsg.c | 8 ++ test/sockets/test_tcp_accept_nonblock.c | 98 +++++++++++++++++++ test/sockets/test_tcp_blocking.c | 73 ++++++++++++++ test/test_sockets_node.py | 16 +++ 11 files changed, 312 insertions(+) create mode 100644 system/lib/libc/musl/src/internal/emscripten_fd_wait.h create mode 100644 test/sockets/test_tcp_accept_nonblock.c create mode 100644 test/sockets/test_tcp_blocking.c diff --git a/ChangeLog.md b/ChangeLog.md index c593b7a9e7869..fed1e966e9a72 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,12 @@ See docs/process.md for more on how version tagging works. level- and edge-triggered modes, `EPOLLONESHOT`, `EPOLLEXCLUSIVE`, `EPOLLRDHUP`, nesting, and blocking waits under `PROXY_TO_PTHREAD`, `ASYNCIFY`, and `JSPI`. (#27207) +- Blocking `accept`, `accept4`, `recv`, `recvfrom` and `recvmsg` on sockets + are now supported under `-pthread` on secondary threads (including `main()` + under `PROXY_TO_PTHREAD`): instead of returning `EAGAIN`, a would-block call + on a blocking socket parks the calling thread until ready. On the main + browser thread, which cannot block, `EAGAIN` still surfaces. `accept4` also + now honors `SOCK_NONBLOCK` on the accepted socket. (#27342) - The deprecated `LEGALIZE_JS_FFI` setting was completely removed and moved to legacy settings. This behaviour of lowering away i64 values at the Wasm bounary is still used when `WASM_BIGINT` is disabled. However diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 89e5cba52aa9b..4fc8158476bd4 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -330,6 +330,7 @@ sigs = { _emscripten_create_wasm_worker__sig: 'iipip', _emscripten_dlopen_js__sig: 'vpppp', _emscripten_dlsync_threads__sig: 'v', + _emscripten_fd_wait__sig: 'iii', _emscripten_fetch_get_response_headers__sig: 'pipp', _emscripten_fetch_get_response_headers_length__sig: 'pi', _emscripten_fs_load_embedded_files__sig: 'vp', diff --git a/src/lib/libsyscall.js b/src/lib/libsyscall.js index 34e65d3435c1f..cbcf3b72c794d 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -420,6 +420,13 @@ var SyscallsLibrary = { assert(!errno); #endif } + // Honor SOCK_NONBLOCK on the accepted fd (SOCK_CLOEXEC is a no-op for a + // single process, matching F_SETFD). Without this the new fd only inherits + // the listener's flags, so a SOCK_NONBLOCK accept off a blocking listener + // would wrongly yield a blocking socket. + if (flags & {{{ cDefs.SOCK_NONBLOCK }}}) { + newsock.stream.flags |= {{{ cDefs.O_NONBLOCK }}}; + } return newsock.stream.fd; }, __syscall_bind__deps: ['$getSocketFromFD', '$getSocketAddress'], @@ -716,6 +723,43 @@ var SyscallsLibrary = { __syscall_poll_nonblocking: (fds, nfds) => { return doPollSync(fds, nfds); }, + // The single wait primitive behind blocking socket data ops. The data + // syscalls themselves are strictly synchronous (single attempt, -EAGAIN when + // they would block); libc's blocking wrappers (compiled only into the -mt + // libc) call this on EAGAIN with a blocking fd and then retry. It blocks only + // on a proxied pthread worker: the sync-proxy completes - ending the worker's + // futex wait - when the returned promise resolves. In every other context + // (including the event-loop thread, which cannot block) it fails with + // -EAGAIN. Resolves 0 once `fd` reports one of `events` (POLL* flags; + // error/hangup/close always wake). Single-threaded builds use epoll instead. +#if !PTHREADS + // Without pthreads the body is just `return -EAGAIN`, which cannot throw; + // skip the syscall try/catch wrapper so closure doesn't flag it as dead. + _emscripten_fd_wait__nothrow: true, +#endif + _emscripten_fd_wait__proxy: 'sync', + _emscripten_fd_wait__async: 'auto', + _emscripten_fd_wait__deps: ['$FS', '$pollOne'], + _emscripten_fd_wait: (fd, events) => { +#if PTHREADS + if (PThread.currentProxiedOperationCallerThread) { + // Must resolve through a Promise: the caller's sync-proxy awaits a + // thenable (PROXY_SYNC_ASYNC), even when already ready. + return new Promise((resolve) => { + if (pollOne(fd, events)) return resolve(0); + var stream = FS.getStream(fd); + if (!stream) return resolve(0); // closed: let the retry surface EBADF + var reg = stream.node.addListener(() => { + if (pollOne(fd, events)) { + reg.listeners.delete(reg.entry); + resolve(0); + } + }); + }); + } +#endif + return -{{{ cDefs.EAGAIN }}}; + }, // epoll: the entry points live here (like every other syscall); the heavy // lifting is in libepoll.js, which they call after resolving the epoll stream. __syscall_epoll_create1__deps: ['$epollNewInstance'], diff --git a/system/lib/libc/musl/src/internal/emscripten_fd_wait.h b/system/lib/libc/musl/src/internal/emscripten_fd_wait.h new file mode 100644 index 0000000000000..5ee0a0c7b627c --- /dev/null +++ b/system/lib/libc/musl/src/internal/emscripten_fd_wait.h @@ -0,0 +1,42 @@ +#ifndef EMSCRIPTEN_FD_WAIT_H +#define EMSCRIPTEN_FD_WAIT_H + +// Blocking socket data ops on emscripten: the underlying JS syscalls are +// strictly synchronous and return -EAGAIN when they would block. For a +// blocking fd the network wrappers wait for readiness via the single blocking +// primitive _emscripten_fd_wait and retry. This is a pthreads-only facility +// (the retry loops compile only into the -mt libc): _emscripten_fd_wait blocks +// by parking a proxied worker on its sync-proxy. Where no stack can wait (the +// event-loop thread itself), the wait fails and the EAGAIN surfaces unchanged. +// Single-threaded JSPI/ASYNCIFY builds use epoll for readiness instead. + +#include +#include +#include +#include "syscall.h" + +int _emscripten_fd_wait(int fd, int events); + +static inline int __emscripten_sock_can_wait(int fd, int dontwait) +{ + if (dontwait) return 0; + int fl = __syscall(SYS_fcntl64, fd, F_GETFL); + return fl >= 0 && !(fl & O_NONBLOCK); +} + +// The blocking-socket retry convention: `attempt` is a strictly synchronous +// __socketcall_cp expression returning -EAGAIN when it would block. On EAGAIN +// with a blocking fd (and no MSG_DONTWAIT), wait for readiness and retry. If +// the wait itself fails (no thread to park on), the EAGAIN surfaces unchanged. +// Yields the raw syscall result; callers apply __syscall_ret. +#define __emscripten_sock_retry_cp(fd, dontwait, attempt) ({ \ + long __r; \ + for (;;) { \ + __r = (attempt); \ + if (__r != -EAGAIN || !__emscripten_sock_can_wait(fd, dontwait) \ + || _emscripten_fd_wait(fd, POLLIN)) break; \ + } \ + __r; \ +}) + +#endif diff --git a/system/lib/libc/musl/src/network/accept.c b/system/lib/libc/musl/src/network/accept.c index a92406fa7315c..addcccb743b27 100644 --- a/system/lib/libc/musl/src/network/accept.c +++ b/system/lib/libc/musl/src/network/accept.c @@ -1,7 +1,15 @@ #include #include "syscall.h" +#ifdef __EMSCRIPTEN_PTHREADS__ +#include "emscripten_fd_wait.h" +#endif int accept(int fd, struct sockaddr *restrict addr, socklen_t *restrict len) { +#ifdef __EMSCRIPTEN_PTHREADS__ + return __syscall_ret(__emscripten_sock_retry_cp(fd, 0, + __socketcall_cp(accept, fd, addr, len, 0, 0, 0))); +#else return socketcall_cp(accept, fd, addr, len, 0, 0, 0); +#endif } diff --git a/system/lib/libc/musl/src/network/accept4.c b/system/lib/libc/musl/src/network/accept4.c index 765a38edc37d8..85cd2e7aac7c4 100644 --- a/system/lib/libc/musl/src/network/accept4.c +++ b/system/lib/libc/musl/src/network/accept4.c @@ -3,11 +3,19 @@ #include #include #include "syscall.h" +#ifdef __EMSCRIPTEN_PTHREADS__ +#include "emscripten_fd_wait.h" +#endif int accept4(int fd, struct sockaddr *restrict addr, socklen_t *restrict len, int flg) { if (!flg) return accept(fd, addr, len); +#ifdef __EMSCRIPTEN_PTHREADS__ + int ret = __syscall_ret(__emscripten_sock_retry_cp(fd, 0, + __socketcall_cp(accept4, fd, addr, len, flg, 0, 0))); +#else int ret = socketcall_cp(accept4, fd, addr, len, flg, 0, 0); +#endif if (ret>=0 || (errno != ENOSYS && errno != EINVAL)) return ret; if (flg & ~(SOCK_CLOEXEC|SOCK_NONBLOCK)) { errno = EINVAL; diff --git a/system/lib/libc/musl/src/network/recvfrom.c b/system/lib/libc/musl/src/network/recvfrom.c index 61911663e0868..c12c6485a40aa 100644 --- a/system/lib/libc/musl/src/network/recvfrom.c +++ b/system/lib/libc/musl/src/network/recvfrom.c @@ -1,7 +1,15 @@ #include #include "syscall.h" +#ifdef __EMSCRIPTEN_PTHREADS__ +#include "emscripten_fd_wait.h" +#endif ssize_t recvfrom(int fd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict alen) { +#ifdef __EMSCRIPTEN_PTHREADS__ + return __syscall_ret(__emscripten_sock_retry_cp(fd, flags & MSG_DONTWAIT, + __socketcall_cp(recvfrom, fd, buf, len, flags, addr, alen))); +#else return socketcall_cp(recvfrom, fd, buf, len, flags, addr, alen); +#endif } diff --git a/system/lib/libc/musl/src/network/recvmsg.c b/system/lib/libc/musl/src/network/recvmsg.c index a973763a85a2e..c89ad8bce0ef9 100644 --- a/system/lib/libc/musl/src/network/recvmsg.c +++ b/system/lib/libc/musl/src/network/recvmsg.c @@ -4,6 +4,9 @@ #include #include #include "syscall.h" +#ifdef __EMSCRIPTEN_PTHREADS__ +#include "emscripten_fd_wait.h" +#endif hidden void __convert_scm_timestamps(struct msghdr *, socklen_t); @@ -59,7 +62,12 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) msg = &h; } #endif +#ifdef __EMSCRIPTEN_PTHREADS__ + r = __syscall_ret(__emscripten_sock_retry_cp(fd, flags & MSG_DONTWAIT, + __socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0))); +#else r = socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0); +#endif if (r >= 0) __convert_scm_timestamps(msg, orig_controllen); #if LONG_MAX > INT_MAX && !defined(__EMSCRIPTEN__) if (orig) *orig = h; diff --git a/test/sockets/test_tcp_accept_nonblock.c b/test/sockets/test_tcp_accept_nonblock.c new file mode 100644 index 0000000000000..2d0438c2bdab9 --- /dev/null +++ b/test/sockets/test_tcp_accept_nonblock.c @@ -0,0 +1,98 @@ +/* + * Copyright 2026 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. + * + * accept4(SOCK_NONBLOCK) must yield a non-blocking accepted socket even off a + * *blocking* listener: the flag is applied on top of the flags inherited from + * the listener, not dropped. A poll()-driven main loop (single-threaded, zero + * timeout) waits for the incoming connection, accept4()s it with SOCK_NONBLOCK, + * then checks F_GETFL reports O_NONBLOCK and that a data-less recv() would-block + * with EAGAIN rather than hanging. Plain POSIX, so it also runs natively. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +static int listen_fd = -1; +static int client_fd = -1; +static int peer_fd = -1; + +static void finish(void) { + if (client_fd >= 0) close(client_fd); + if (peer_fd >= 0) close(peer_fd); + if (listen_fd >= 0) close(listen_fd); + printf("done\n"); +#ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); +#endif +} + +static void main_loop(void) { + struct pollfd pfd = { .fd = listen_fd, .events = POLLIN }; + if (poll(&pfd, 1, 0) <= 0 || !(pfd.revents & POLLIN)) { + return; // no connection queued yet + } + + // The listener is blocking (never marked O_NONBLOCK), so inheritance alone + // would give a blocking socket; SOCK_NONBLOCK must override that. + peer_fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK); + assert(peer_fd >= 0); + + int fl = fcntl(peer_fd, F_GETFL); + assert(fl >= 0 && (fl & O_NONBLOCK) && "accept4 SOCK_NONBLOCK not honored"); + + // A non-blocking recv with no data pending returns EAGAIN immediately instead + // of blocking, confirming the fd is really non-blocking. + char buf[4]; + ssize_t n = recv(peer_fd, buf, sizeof(buf), 0); + assert(n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)); + + finish(); +} + +int main(void) { + listen_fd = socket(AF_INET, SOCK_STREAM, 0); + assert(listen_fd >= 0); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + assert(bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) == 0); + socklen_t l = sizeof(addr); + assert(getsockname(listen_fd, (struct sockaddr*)&addr, &l) == 0); + assert(listen(listen_fd, 4) == 0); + // Deliberately leave listen_fd blocking to prove SOCK_NONBLOCK is applied on + // top of the inherited (blocking) listener flags. + + client_fd = socket(AF_INET, SOCK_STREAM, 0); + assert(client_fd >= 0); + fcntl(client_fd, F_SETFL, O_NONBLOCK); + int r = connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)); + assert(r == 0 || errno == EINPROGRESS); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(main_loop, 0, 0); +#else + while (peer_fd < 0) { + main_loop(); + usleep(1000); + } +#endif + return 0; +} diff --git a/test/sockets/test_tcp_blocking.c b/test/sockets/test_tcp_blocking.c new file mode 100644 index 0000000000000..dbd4216f3878e --- /dev/null +++ b/test/sockets/test_tcp_blocking.c @@ -0,0 +1,73 @@ +/* + * Copyright 2026 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. + * + * Blocking TCP loopback ping/pong exercising the _emscripten_fd_wait primitive: + * a *blocking* accept() and a *blocking* recv() that each have to suspend. The + * client connects from a separate thread after a delay, so the server's accept + * and recv both would-block first and can only complete by being woken through + * the inode readiness wait-queue (the SOCKFS.emit bridge). Under + * PROXY_TO_PTHREAD every blocking call parks its proxied worker on the + * sync-proxy; the main-thread event loop drives node's sockets and delivers the + * wakes. send()/write() never block (node buffers), so only the read side waits. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct sockaddr_in server_addr; + +static void* client_thread(void* arg) { + usleep(100000); // let the server block in accept() first + int fd = socket(AF_INET, SOCK_STREAM, 0); + assert(fd >= 0); + assert(connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == 0); + assert(send(fd, "ping", 4, 0) == 4); // buffered, never blocks + char buf[4]; + assert(recv(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "pong", 4) == 0); + close(fd); + return NULL; +} + +int main(void) { + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + assert(listen_fd >= 0); + + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); + assert(bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == 0); + socklen_t l = sizeof(server_addr); + assert(getsockname(listen_fd, (struct sockaddr*)&server_addr, &l) == 0); + assert(listen(listen_fd, 4) == 0); + + pthread_t t; + assert(pthread_create(&t, NULL, client_thread, NULL) == 0); + + // Blocking accept(): no connection is pending yet (the client sleeps first), + // so it suspends the proxied worker on the listener's readiness queue until + // the client connects. + struct sockaddr_in ca; + socklen_t cl = sizeof(ca); + int peer_fd = accept(listen_fd, (struct sockaddr*)&ca, &cl); + assert(peer_fd >= 0); + + // Blocking recv(): suspends until the client's "ping" arrives. + char buf[4]; + assert(recv(peer_fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "ping", 4) == 0); + assert(send(peer_fd, "pong", 4, 0) == 4); + + assert(pthread_join(t, NULL) == 0); + close(peer_fd); + close(listen_fd); + printf("done\n"); + return 0; +} diff --git a/test/test_sockets_node.py b/test/test_sockets_node.py index 97750c862641c..07871fdfaaaad 100644 --- a/test/test_sockets_node.py +++ b/test/test_sockets_node.py @@ -218,6 +218,22 @@ def test_noderawsockets_epoll_socket_blocking_jspi(self): self.do_runf('sockets/test_epoll_socket_blocking.c', 'done\n', cflags=['-sNODERAWSOCKETS', '-sEXIT_RUNTIME']) + def test_noderawsockets_tcp_blocking(self): + # Blocking accept() + recv() via the _emscripten_fd_wait primitive: the + # client connects from another thread after a delay so both would-block + # first and can only complete by being woken. This is a pthreads-only + # facility (the retry loops compile only into the -mt libc), so it runs + # under PROXY_TO_PTHREAD, where each blocking call parks its proxied worker. + self.do_runf('sockets/test_tcp_blocking.c', 'done\n', + cflags=['-sNODERAWSOCKETS', '-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME']) + + def test_noderawsockets_tcp_accept_nonblock(self): + # accept4(SOCK_NONBLOCK) off a blocking listener yields a non-blocking fd + # (the flag is applied on top of the inherited listener flags). Single + # threaded, poll()-driven, so no fd_wait blocking is involved. + self.do_runf('sockets/test_tcp_accept_nonblock.c', 'done\n', + cflags=['-sNODERAWSOCKETS', '-sEXIT_RUNTIME']) + def test_noderawsockets_epoll_rdhup(self): # A blocking epoll_wait reports EPOLLRDHUP when the TCP peer half-closes its # write side (FIN), distinct from a full EPOLLHUP, and only when requested. From 44304212b0005028b7cc2d8f4deb97064402ac15 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 19 Aug 2026 20:39:33 -0700 Subject: [PATCH 2/5] Declare _emscripten_fd_wait in emscripten_internal.h for gen_sig_info --- system/lib/libc/emscripten_internal.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/lib/libc/emscripten_internal.h b/system/lib/libc/emscripten_internal.h index 55ca0fa09dc2a..a4660ec7d0ed0 100644 --- a/system/lib/libc/emscripten_internal.h +++ b/system/lib/libc/emscripten_internal.h @@ -102,6 +102,9 @@ void* _dlsym_catchup_js(struct dso* handle, int sym_index); int _setitimer_js(int which, double timeout); +// Blocking wait for fd readiness; see _emscripten_fd_wait in libsyscall.js. +int _emscripten_fd_wait(int fd, int events); + // Synchronize loaded modules across threads. // Runs _emscripten_dlsync_self on each of the threads that are running at // the time of the call. From b740cea8aa4c047b361b1f2f4bc40cf3f45921c1 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 23 Aug 2026 10:12:43 -0700 Subject: [PATCH 3/5] Address review: fd_wait in libfs.js, boolean result, ASYNCIFY/JSPI support - Move _emscripten_fd_wait to libfs.js (not a syscall; drops __nothrow). - Fold the O_NONBLOCK check into the primitive and return a boolean, so the libc retry loop makes a single JS call. - Support single-threaded ASYNCIFY/JSPI by suspending the calling stack, mirroring __syscall_poll; the retry loop now compiles into every libc variant, with a no-op stub for WASMFS. - Declare _emscripten_fd_wait only in emscripten_internal.h; add the copyright header; drop the _cp suffix from the retry macro. - Move the ChangeLog entry to 6.0.9; add a JSPI variant of the blocking test. - Rebaseline hello_dylink_all codesize. --- ChangeLog.md | 13 ++-- src/lib/libfs.js | 34 ++++++++++ src/lib/libsyscall.js | 37 ----------- system/lib/libc/emscripten_internal.h | 2 +- .../musl/src/internal/emscripten_fd_wait.h | 42 +++++------- system/lib/libc/musl/src/network/accept.c | 8 +-- system/lib/libc/musl/src/network/accept4.c | 8 +-- system/lib/libc/musl/src/network/recvfrom.c | 8 +-- system/lib/libc/musl/src/network/recvmsg.c | 8 +-- system/lib/wasmfs/syscalls.cpp | 4 ++ .../test_codesize_hello_dylink_all.json | 5 +- test/sockets/test_tcp_blocking.c | 65 ++++++++++++++----- test/test_sockets_node.py | 11 +++- 13 files changed, 127 insertions(+), 118 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index fed1e966e9a72..b4bb47697cae1 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,13 @@ See docs/process.md for more on how version tagging works. 6.0.9 (in development) ---------------------- +- Blocking `accept`, `accept4`, `recv`, `recvfrom` and `recvmsg` on sockets + are now supported wherever the calling stack can suspend: on secondary + threads under `-pthread` (including `main()` under `PROXY_TO_PTHREAD`), and + under `ASYNCIFY` and `JSPI`. Instead of returning `EAGAIN`, a would-block call + on a blocking socket waits until ready. On a main browser thread that cannot + suspend, `EAGAIN` still surfaces. `accept4` also now honors `SOCK_NONBLOCK` + on the accepted socket. (#27342) 6.0.8 - 08/20/26 ---------------- @@ -30,12 +37,6 @@ See docs/process.md for more on how version tagging works. level- and edge-triggered modes, `EPOLLONESHOT`, `EPOLLEXCLUSIVE`, `EPOLLRDHUP`, nesting, and blocking waits under `PROXY_TO_PTHREAD`, `ASYNCIFY`, and `JSPI`. (#27207) -- Blocking `accept`, `accept4`, `recv`, `recvfrom` and `recvmsg` on sockets - are now supported under `-pthread` on secondary threads (including `main()` - under `PROXY_TO_PTHREAD`): instead of returning `EAGAIN`, a would-block call - on a blocking socket parks the calling thread until ready. On the main - browser thread, which cannot block, `EAGAIN` still surfaces. `accept4` also - now honors `SOCK_NONBLOCK` on the accepted socket. (#27342) - The deprecated `LEGALIZE_JS_FFI` setting was completely removed and moved to legacy settings. This behaviour of lowering away i64 values at the Wasm bounary is still used when `WASM_BIGINT` is disabled. However diff --git a/src/lib/libfs.js b/src/lib/libfs.js index 4907180698353..2bf4f5646e28e 100644 --- a/src/lib/libfs.js +++ b/src/lib/libfs.js @@ -1963,6 +1963,40 @@ FS.staticInit();`; }, }, + // The wait primitive behind blocking socket data ops. The data syscalls are + // strictly synchronous (single attempt, -EAGAIN when they would block); on + // EAGAIN libc's wrappers call this and retry when it returns true. Returns + // false when `fd` is non-blocking or there is no stack to suspend (the + // event-loop thread outside ASYNCIFY/JSPI), so the EAGAIN surfaces. Otherwise + // waits until `fd` reports one of `events` (POLL* flags; error/hangup/close + // always wake): a proxied pthread worker parks on its sync-proxy, an + // ASYNCIFY/JSPI stack suspends. + _emscripten_fd_wait__proxy: 'sync', + _emscripten_fd_wait__async: 'auto', + _emscripten_fd_wait__deps: ['$FS', '$pollOne'], + _emscripten_fd_wait: (fd, events) => { +#if PTHREADS + if (!PThread.currentProxiedOperationCallerThread) return 0; +#elif !ASYNCIFY + return 0; +#endif +#if PTHREADS || ASYNCIFY + // Always a Promise: a proxied caller's sync-proxy awaits a thenable. + return new Promise((resolve) => { + var stream = FS.getStream(fd); + if (!stream) return resolve(1); // closed: let the retry surface EBADF + if (stream.flags & {{{ cDefs.O_NONBLOCK }}}) return resolve(0); + if (pollOne(fd, events)) return resolve(1); + var reg = stream.node.addListener(() => { + if (pollOne(fd, events)) { + reg.listeners.delete(reg.entry); + resolve(1); + } + }); + }); +#endif + }, + $FS_mkdirTree__docs: ` /** * @param {number=} mode Optionally, the mode to create in. Uses mkdir's diff --git a/src/lib/libsyscall.js b/src/lib/libsyscall.js index cbcf3b72c794d..7b021bd4676b3 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -723,43 +723,6 @@ var SyscallsLibrary = { __syscall_poll_nonblocking: (fds, nfds) => { return doPollSync(fds, nfds); }, - // The single wait primitive behind blocking socket data ops. The data - // syscalls themselves are strictly synchronous (single attempt, -EAGAIN when - // they would block); libc's blocking wrappers (compiled only into the -mt - // libc) call this on EAGAIN with a blocking fd and then retry. It blocks only - // on a proxied pthread worker: the sync-proxy completes - ending the worker's - // futex wait - when the returned promise resolves. In every other context - // (including the event-loop thread, which cannot block) it fails with - // -EAGAIN. Resolves 0 once `fd` reports one of `events` (POLL* flags; - // error/hangup/close always wake). Single-threaded builds use epoll instead. -#if !PTHREADS - // Without pthreads the body is just `return -EAGAIN`, which cannot throw; - // skip the syscall try/catch wrapper so closure doesn't flag it as dead. - _emscripten_fd_wait__nothrow: true, -#endif - _emscripten_fd_wait__proxy: 'sync', - _emscripten_fd_wait__async: 'auto', - _emscripten_fd_wait__deps: ['$FS', '$pollOne'], - _emscripten_fd_wait: (fd, events) => { -#if PTHREADS - if (PThread.currentProxiedOperationCallerThread) { - // Must resolve through a Promise: the caller's sync-proxy awaits a - // thenable (PROXY_SYNC_ASYNC), even when already ready. - return new Promise((resolve) => { - if (pollOne(fd, events)) return resolve(0); - var stream = FS.getStream(fd); - if (!stream) return resolve(0); // closed: let the retry surface EBADF - var reg = stream.node.addListener(() => { - if (pollOne(fd, events)) { - reg.listeners.delete(reg.entry); - resolve(0); - } - }); - }); - } -#endif - return -{{{ cDefs.EAGAIN }}}; - }, // epoll: the entry points live here (like every other syscall); the heavy // lifting is in libepoll.js, which they call after resolving the epoll stream. __syscall_epoll_create1__deps: ['$epollNewInstance'], diff --git a/system/lib/libc/emscripten_internal.h b/system/lib/libc/emscripten_internal.h index a4660ec7d0ed0..e6ac50584708c 100644 --- a/system/lib/libc/emscripten_internal.h +++ b/system/lib/libc/emscripten_internal.h @@ -102,7 +102,7 @@ void* _dlsym_catchup_js(struct dso* handle, int sym_index); int _setitimer_js(int which, double timeout); -// Blocking wait for fd readiness; see _emscripten_fd_wait in libsyscall.js. +// Wait for fd readiness if the calling stack can suspend; see libfs.js. int _emscripten_fd_wait(int fd, int events); // Synchronize loaded modules across threads. diff --git a/system/lib/libc/musl/src/internal/emscripten_fd_wait.h b/system/lib/libc/musl/src/internal/emscripten_fd_wait.h index 5ee0a0c7b627c..40643f736531d 100644 --- a/system/lib/libc/musl/src/internal/emscripten_fd_wait.h +++ b/system/lib/libc/musl/src/internal/emscripten_fd_wait.h @@ -1,40 +1,34 @@ +/* + * Copyright 2026 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. + */ + #ifndef EMSCRIPTEN_FD_WAIT_H #define EMSCRIPTEN_FD_WAIT_H // Blocking socket data ops on emscripten: the underlying JS syscalls are -// strictly synchronous and return -EAGAIN when they would block. For a -// blocking fd the network wrappers wait for readiness via the single blocking -// primitive _emscripten_fd_wait and retry. This is a pthreads-only facility -// (the retry loops compile only into the -mt libc): _emscripten_fd_wait blocks -// by parking a proxied worker on its sync-proxy. Where no stack can wait (the -// event-loop thread itself), the wait fails and the EAGAIN surfaces unchanged. -// Single-threaded JSPI/ASYNCIFY builds use epoll for readiness instead. +// strictly synchronous and return -EAGAIN when they would block. The network +// wrappers then wait for readiness via _emscripten_fd_wait and retry. The wait +// itself only succeeds where a stack can be suspended - a proxied pthread +// worker parked on its sync-proxy, or an ASYNCIFY/JSPI stack - and only for a +// blocking fd; otherwise it returns false and the EAGAIN surfaces unchanged. -#include #include #include +#include "emscripten_internal.h" #include "syscall.h" -int _emscripten_fd_wait(int fd, int events); - -static inline int __emscripten_sock_can_wait(int fd, int dontwait) -{ - if (dontwait) return 0; - int fl = __syscall(SYS_fcntl64, fd, F_GETFL); - return fl >= 0 && !(fl & O_NONBLOCK); -} - -// The blocking-socket retry convention: `attempt` is a strictly synchronous -// __socketcall_cp expression returning -EAGAIN when it would block. On EAGAIN -// with a blocking fd (and no MSG_DONTWAIT), wait for readiness and retry. If -// the wait itself fails (no thread to park on), the EAGAIN surfaces unchanged. +// `attempt` is a strictly synchronous __socketcall_cp expression returning +// -EAGAIN when it would block; `dontwait` (MSG_DONTWAIT) suppresses the wait. // Yields the raw syscall result; callers apply __syscall_ret. -#define __emscripten_sock_retry_cp(fd, dontwait, attempt) ({ \ +#define __emscripten_sock_retry(fd, dontwait, attempt) ({ \ long __r; \ for (;;) { \ __r = (attempt); \ - if (__r != -EAGAIN || !__emscripten_sock_can_wait(fd, dontwait) \ - || _emscripten_fd_wait(fd, POLLIN)) break; \ + if (__r != -EAGAIN || (dontwait) || !_emscripten_fd_wait(fd, POLLIN)) \ + break; \ } \ __r; \ }) diff --git a/system/lib/libc/musl/src/network/accept.c b/system/lib/libc/musl/src/network/accept.c index addcccb743b27..59c7a2dcc8cd0 100644 --- a/system/lib/libc/musl/src/network/accept.c +++ b/system/lib/libc/musl/src/network/accept.c @@ -1,15 +1,9 @@ #include #include "syscall.h" -#ifdef __EMSCRIPTEN_PTHREADS__ #include "emscripten_fd_wait.h" -#endif int accept(int fd, struct sockaddr *restrict addr, socklen_t *restrict len) { -#ifdef __EMSCRIPTEN_PTHREADS__ - return __syscall_ret(__emscripten_sock_retry_cp(fd, 0, + return __syscall_ret(__emscripten_sock_retry(fd, 0, __socketcall_cp(accept, fd, addr, len, 0, 0, 0))); -#else - return socketcall_cp(accept, fd, addr, len, 0, 0, 0); -#endif } diff --git a/system/lib/libc/musl/src/network/accept4.c b/system/lib/libc/musl/src/network/accept4.c index 85cd2e7aac7c4..79e3b645b8ac9 100644 --- a/system/lib/libc/musl/src/network/accept4.c +++ b/system/lib/libc/musl/src/network/accept4.c @@ -3,19 +3,13 @@ #include #include #include "syscall.h" -#ifdef __EMSCRIPTEN_PTHREADS__ #include "emscripten_fd_wait.h" -#endif int accept4(int fd, struct sockaddr *restrict addr, socklen_t *restrict len, int flg) { if (!flg) return accept(fd, addr, len); -#ifdef __EMSCRIPTEN_PTHREADS__ - int ret = __syscall_ret(__emscripten_sock_retry_cp(fd, 0, + int ret = __syscall_ret(__emscripten_sock_retry(fd, 0, __socketcall_cp(accept4, fd, addr, len, flg, 0, 0))); -#else - int ret = socketcall_cp(accept4, fd, addr, len, flg, 0, 0); -#endif if (ret>=0 || (errno != ENOSYS && errno != EINVAL)) return ret; if (flg & ~(SOCK_CLOEXEC|SOCK_NONBLOCK)) { errno = EINVAL; diff --git a/system/lib/libc/musl/src/network/recvfrom.c b/system/lib/libc/musl/src/network/recvfrom.c index c12c6485a40aa..ee34095f99518 100644 --- a/system/lib/libc/musl/src/network/recvfrom.c +++ b/system/lib/libc/musl/src/network/recvfrom.c @@ -1,15 +1,9 @@ #include #include "syscall.h" -#ifdef __EMSCRIPTEN_PTHREADS__ #include "emscripten_fd_wait.h" -#endif ssize_t recvfrom(int fd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict alen) { -#ifdef __EMSCRIPTEN_PTHREADS__ - return __syscall_ret(__emscripten_sock_retry_cp(fd, flags & MSG_DONTWAIT, + return __syscall_ret(__emscripten_sock_retry(fd, flags & MSG_DONTWAIT, __socketcall_cp(recvfrom, fd, buf, len, flags, addr, alen))); -#else - return socketcall_cp(recvfrom, fd, buf, len, flags, addr, alen); -#endif } diff --git a/system/lib/libc/musl/src/network/recvmsg.c b/system/lib/libc/musl/src/network/recvmsg.c index c89ad8bce0ef9..1a788e6c1a53f 100644 --- a/system/lib/libc/musl/src/network/recvmsg.c +++ b/system/lib/libc/musl/src/network/recvmsg.c @@ -4,9 +4,7 @@ #include #include #include "syscall.h" -#ifdef __EMSCRIPTEN_PTHREADS__ #include "emscripten_fd_wait.h" -#endif hidden void __convert_scm_timestamps(struct msghdr *, socklen_t); @@ -62,12 +60,8 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) msg = &h; } #endif -#ifdef __EMSCRIPTEN_PTHREADS__ - r = __syscall_ret(__emscripten_sock_retry_cp(fd, flags & MSG_DONTWAIT, + r = __syscall_ret(__emscripten_sock_retry(fd, flags & MSG_DONTWAIT, __socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0))); -#else - r = socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0); -#endif if (r >= 0) __convert_scm_timestamps(msg, orig_controllen); #if LONG_MAX > INT_MAX && !defined(__EMSCRIPTEN__) if (orig) *orig = h; diff --git a/system/lib/wasmfs/syscalls.cpp b/system/lib/wasmfs/syscalls.cpp index 8d098b12d2213..46c20baa537ce 100644 --- a/system/lib/wasmfs/syscalls.cpp +++ b/system/lib/wasmfs/syscalls.cpp @@ -1732,6 +1732,10 @@ int _munmap_js( // Stubs (at least for now) +// Socket readiness wait used by libc's blocking socket wrappers; WASMFS has no +// socket support, so there is never anything to wait for. +int _emscripten_fd_wait(int fd, int events) { return 0; } + int __syscall_accept4(int sockfd, struct sockaddr* addr, socklen_t* len, diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index b1aad64974b3a..643f13ddec856 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": 270568, + "a.out.js": 270648, "a.out.nodebug.wasm": 588318, - "total": 858886, + "total": 858966, "sent": [ "IMG_Init", "IMG_Load", @@ -287,6 +287,7 @@ "_dlsym_catchup_js", "_dlsym_js", "_emscripten_dlopen_js", + "_emscripten_fd_wait", "_emscripten_fs_load_embedded_files", "_emscripten_get_last_devicemotion_event", "_emscripten_get_last_deviceorientation_event", diff --git a/test/sockets/test_tcp_blocking.c b/test/sockets/test_tcp_blocking.c index dbd4216f3878e..6432eaa8d9251 100644 --- a/test/sockets/test_tcp_blocking.c +++ b/test/sockets/test_tcp_blocking.c @@ -4,38 +4,60 @@ * University of Illinois/NCSA Open Source License. Both these licenses can be * found in the LICENSE file. * - * Blocking TCP loopback ping/pong exercising the _emscripten_fd_wait primitive: - * a *blocking* accept() and a *blocking* recv() that each have to suspend. The - * client connects from a separate thread after a delay, so the server's accept - * and recv both would-block first and can only complete by being woken through - * the inode readiness wait-queue (the SOCKFS.emit bridge). Under - * PROXY_TO_PTHREAD every blocking call parks its proxied worker on the - * sync-proxy; the main-thread event loop drives node's sockets and delivers the + * Blocking TCP loopback exercising the _emscripten_fd_wait primitive: a + * *blocking* accept() and a *blocking* recv() that each have to suspend. The + * client connects on a delay (from a separate thread under -pthread, or a timer + * under JSPI), so the server's accept and recv both would-block first and can + * only complete by being woken through the inode readiness wait-queue (the + * SOCKFS.emit bridge). Under PROXY_TO_PTHREAD every blocking call parks its + * proxied worker on the sync-proxy; under JSPI the calling stack suspends. In + * both cases the main-thread event loop drives node's sockets and delivers the * wakes. send()/write() never block (node buffers), so only the read side waits. */ #include #include +#include +#include +#include #include -#include #include #include #include #include +#ifdef __EMSCRIPTEN_PTHREADS__ +#include +#endif + static struct sockaddr_in server_addr; +static int client_fd; + +static void client_connect(void* arg) { + client_fd = socket(AF_INET, SOCK_STREAM, 0); + assert(client_fd >= 0); +#ifndef __EMSCRIPTEN_PTHREADS__ + // Runs on the event-loop thread while main() is suspended, so it must not + // block itself: connect asynchronously, node buffers the send until open. + fcntl(client_fd, F_SETFL, O_NONBLOCK); + int r = connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); + assert(r == 0 || errno == EINPROGRESS); +#else + assert(connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == 0); +#endif + assert(send(client_fd, "ping", 4, 0) == 4); // buffered, never blocks +} +#ifdef __EMSCRIPTEN_PTHREADS__ static void* client_thread(void* arg) { usleep(100000); // let the server block in accept() first - int fd = socket(AF_INET, SOCK_STREAM, 0); - assert(fd >= 0); - assert(connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == 0); - assert(send(fd, "ping", 4, 0) == 4); // buffered, never blocks + client_connect(NULL); char buf[4]; - assert(recv(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "pong", 4) == 0); - close(fd); + assert(recv(client_fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "pong", 4) == 0); + close(client_fd); return NULL; } +#endif int main(void) { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); @@ -49,12 +71,17 @@ int main(void) { assert(getsockname(listen_fd, (struct sockaddr*)&server_addr, &l) == 0); assert(listen(listen_fd, 4) == 0); +#ifdef __EMSCRIPTEN_PTHREADS__ + // Under PROXY_TO_PTHREAD main() runs on a worker that parks in accept(), so + // its event loop can't fire a timer - the wake is a cross-thread notify. pthread_t t; assert(pthread_create(&t, NULL, client_thread, NULL) == 0); +#else + emscripten_async_call(client_connect, NULL, 100); +#endif - // Blocking accept(): no connection is pending yet (the client sleeps first), - // so it suspends the proxied worker on the listener's readiness queue until - // the client connects. + // Blocking accept(): no connection is pending yet (the client waits first), + // so it suspends on the listener's readiness queue until the client connects. struct sockaddr_in ca; socklen_t cl = sizeof(ca); int peer_fd = accept(listen_fd, (struct sockaddr*)&ca, &cl); @@ -65,7 +92,11 @@ int main(void) { assert(recv(peer_fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "ping", 4) == 0); assert(send(peer_fd, "pong", 4, 0) == 4); +#ifdef __EMSCRIPTEN_PTHREADS__ assert(pthread_join(t, NULL) == 0); +#else + close(client_fd); +#endif close(peer_fd); close(listen_fd); printf("done\n"); diff --git a/test/test_sockets_node.py b/test/test_sockets_node.py index 07871fdfaaaad..16b89409f3fb3 100644 --- a/test/test_sockets_node.py +++ b/test/test_sockets_node.py @@ -221,12 +221,17 @@ def test_noderawsockets_epoll_socket_blocking_jspi(self): def test_noderawsockets_tcp_blocking(self): # Blocking accept() + recv() via the _emscripten_fd_wait primitive: the # client connects from another thread after a delay so both would-block - # first and can only complete by being woken. This is a pthreads-only - # facility (the retry loops compile only into the -mt libc), so it runs - # under PROXY_TO_PTHREAD, where each blocking call parks its proxied worker. + # first and can only complete by being woken, with main() proxied to a + # worker so each blocking call can park it. self.do_runf('sockets/test_tcp_blocking.c', 'done\n', cflags=['-sNODERAWSOCKETS', '-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME']) + @requires_jspi_node + def test_noderawsockets_tcp_blocking_jspi(self): + # Same, but the blocking accept()/recv() suspend the wasm stack under JSPI. + self.do_runf('sockets/test_tcp_blocking.c', 'done\n', + cflags=['-sNODERAWSOCKETS', '-sEXIT_RUNTIME']) + def test_noderawsockets_tcp_accept_nonblock(self): # accept4(SOCK_NONBLOCK) off a blocking listener yields a non-blocking fd # (the flag is applied on top of the inherited listener flags). Single From c1a7e9e241c36b87a927176a1d8f2edb332369e6 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 23 Aug 2026 10:49:29 -0700 Subject: [PATCH 4/5] rebaseline --- test/codesize/test_codesize_hello_dylink_all.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 643f13ddec856..84fbe19f480cd 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": 270648, - "a.out.nodebug.wasm": 588318, - "total": 858966, + "a.out.nodebug.wasm": 588458, + "total": 859106, "sent": [ "IMG_Init", "IMG_Load", @@ -1819,6 +1819,7 @@ "env._dlopen_js", "env._dlsym_js", "env._emscripten_dlopen_js", + "env._emscripten_fd_wait", "env._emscripten_get_last_devicemotion_event", "env._emscripten_get_last_deviceorientation_event", "env._emscripten_get_last_mouse_event", From 3c24e29dda153c1654a9fac82d0dbe6adcbf6fec Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 23 Aug 2026 12:40:39 -0700 Subject: [PATCH 5/5] Keep upstream musl bodies under #else; hoist fd_wait early-outs out of the Promise --- src/lib/libfs.js | 9 +++++---- system/lib/libc/musl/src/network/accept.c | 6 ++++++ system/lib/libc/musl/src/network/accept4.c | 6 ++++++ system/lib/libc/musl/src/network/recvfrom.c | 6 ++++++ system/lib/libc/musl/src/network/recvmsg.c | 6 ++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/lib/libfs.js b/src/lib/libfs.js index 2bf4f5646e28e..c6e2df2ec975e 100644 --- a/src/lib/libfs.js +++ b/src/lib/libfs.js @@ -1981,11 +1981,12 @@ FS.staticInit();`; return 0; #endif #if PTHREADS || ASYNCIFY - // Always a Promise: a proxied caller's sync-proxy awaits a thenable. + // Always a Promise from here: a proxied caller's sync-proxy awaits a + // thenable. + var stream = FS.getStream(fd); + if (!stream) return Promise.resolve(1); // closed: let the retry surface EBADF + if (stream.flags & {{{ cDefs.O_NONBLOCK }}}) return Promise.resolve(0); return new Promise((resolve) => { - var stream = FS.getStream(fd); - if (!stream) return resolve(1); // closed: let the retry surface EBADF - if (stream.flags & {{{ cDefs.O_NONBLOCK }}}) return resolve(0); if (pollOne(fd, events)) return resolve(1); var reg = stream.node.addListener(() => { if (pollOne(fd, events)) { diff --git a/system/lib/libc/musl/src/network/accept.c b/system/lib/libc/musl/src/network/accept.c index 59c7a2dcc8cd0..c61638fed8cd5 100644 --- a/system/lib/libc/musl/src/network/accept.c +++ b/system/lib/libc/musl/src/network/accept.c @@ -1,9 +1,15 @@ #include #include "syscall.h" +#ifdef __EMSCRIPTEN__ #include "emscripten_fd_wait.h" +#endif int accept(int fd, struct sockaddr *restrict addr, socklen_t *restrict len) { +#ifdef __EMSCRIPTEN__ return __syscall_ret(__emscripten_sock_retry(fd, 0, __socketcall_cp(accept, fd, addr, len, 0, 0, 0))); +#else + return socketcall_cp(accept, fd, addr, len, 0, 0, 0); +#endif } diff --git a/system/lib/libc/musl/src/network/accept4.c b/system/lib/libc/musl/src/network/accept4.c index 79e3b645b8ac9..e68f4eecb6ff6 100644 --- a/system/lib/libc/musl/src/network/accept4.c +++ b/system/lib/libc/musl/src/network/accept4.c @@ -3,13 +3,19 @@ #include #include #include "syscall.h" +#ifdef __EMSCRIPTEN__ #include "emscripten_fd_wait.h" +#endif int accept4(int fd, struct sockaddr *restrict addr, socklen_t *restrict len, int flg) { if (!flg) return accept(fd, addr, len); +#ifdef __EMSCRIPTEN__ int ret = __syscall_ret(__emscripten_sock_retry(fd, 0, __socketcall_cp(accept4, fd, addr, len, flg, 0, 0))); +#else + int ret = socketcall_cp(accept4, fd, addr, len, flg, 0, 0); +#endif if (ret>=0 || (errno != ENOSYS && errno != EINVAL)) return ret; if (flg & ~(SOCK_CLOEXEC|SOCK_NONBLOCK)) { errno = EINVAL; diff --git a/system/lib/libc/musl/src/network/recvfrom.c b/system/lib/libc/musl/src/network/recvfrom.c index ee34095f99518..83a1d2795e384 100644 --- a/system/lib/libc/musl/src/network/recvfrom.c +++ b/system/lib/libc/musl/src/network/recvfrom.c @@ -1,9 +1,15 @@ #include #include "syscall.h" +#ifdef __EMSCRIPTEN__ #include "emscripten_fd_wait.h" +#endif ssize_t recvfrom(int fd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict alen) { +#ifdef __EMSCRIPTEN__ return __syscall_ret(__emscripten_sock_retry(fd, flags & MSG_DONTWAIT, __socketcall_cp(recvfrom, fd, buf, len, flags, addr, alen))); +#else + return socketcall_cp(recvfrom, fd, buf, len, flags, addr, alen); +#endif } diff --git a/system/lib/libc/musl/src/network/recvmsg.c b/system/lib/libc/musl/src/network/recvmsg.c index 1a788e6c1a53f..34f5f783d5c91 100644 --- a/system/lib/libc/musl/src/network/recvmsg.c +++ b/system/lib/libc/musl/src/network/recvmsg.c @@ -4,7 +4,9 @@ #include #include #include "syscall.h" +#ifdef __EMSCRIPTEN__ #include "emscripten_fd_wait.h" +#endif hidden void __convert_scm_timestamps(struct msghdr *, socklen_t); @@ -60,8 +62,12 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) msg = &h; } #endif +#ifdef __EMSCRIPTEN__ r = __syscall_ret(__emscripten_sock_retry(fd, flags & MSG_DONTWAIT, __socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0))); +#else + r = socketcall_cp(recvmsg, fd, msg, flags, 0, 0, 0); +#endif if (r >= 0) __convert_scm_timestamps(msg, orig_controllen); #if LONG_MAX > INT_MAX && !defined(__EMSCRIPTEN__) if (orig) *orig = h;