From 912731091bfc0794f7284022e2782d7e9cf6e3cb Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Sat, 21 Mar 2026 09:41:24 -0300 Subject: [PATCH] Fix Asyncify.handleAsync conflict with PROXY_SYNC_ASYNC When in a proxied context, skip the Asyncify unwind and call startAsync() directly, letting the proxy mechanism handle the async return. This already affects __syscall_poll and would also affect the new __syscall_ppoll. --- src/lib/libasync.js | 15 +++-- .../test_poll_blocking_asyncify_pthread.c | 60 +++++++++++++++++++ test/test_core.py | 6 ++ 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 test/core/test_poll_blocking_asyncify_pthread.c diff --git a/src/lib/libasync.js b/src/lib/libasync.js index 17ca2c0b011d4..192aa0f94689d 100644 --- a/src/lib/libasync.js +++ b/src/lib/libasync.js @@ -443,10 +443,17 @@ addToLibrary({ // // This is particularly useful for native JS `async` functions where the // returned value will "just work" and be passed back to C++. - handleAsync: (startAsync) => Asyncify.handleSleep(async (wakeUp) => { - // TODO: add error handling as a second param when handleSleep implements it. - wakeUp(await startAsync()); - }), + handleAsync: (startAsync) => { +#if PTHREADS + // When called from a proxied function (PROXY_SYNC_ASYNC), the proxy + // mechanism handles the async return. Skip the Asyncify unwind. + if (PThread.currentProxiedOperationCallerThread) return startAsync(); +#endif + return Asyncify.handleSleep(async (wakeUp) => { + // TODO: add error handling as a second param when handleSleep implements it. + wakeUp(await startAsync()); + }); + }, #elif ASYNCIFY == 2 // diff --git a/test/core/test_poll_blocking_asyncify_pthread.c b/test/core/test_poll_blocking_asyncify_pthread.c new file mode 100644 index 0000000000000..b106dc1ac0938 --- /dev/null +++ b/test/core/test_poll_blocking_asyncify_pthread.c @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +static int fds[2]; + +static void *writer(void *arg) { + write(fds[1], "x", 1); + return NULL; +} + +int main(void) { + pthread_t t; + char buf; + struct pollfd pfd = {.events = POLLIN}; + + pipe(fds); + pfd.fd = fds[0]; + + // poll should timeout on an empty pipe + assert(poll(&pfd, 1, 100) == 0); + + // poll should return immediately when data is already available + write(fds[1], "a", 1); + assert(poll(&pfd, 1, 1000) == 1); + assert(pfd.revents & POLLIN); + assert(read(fds[0], &buf, 1) == 1 && buf == 'a'); + + // poll should wake up from a cross-thread write + pfd.revents = 0; + pthread_create(&t, NULL, writer, NULL); + assert(poll(&pfd, 1, 5000) == 1); + assert(pfd.revents & POLLIN); + assert(read(fds[0], &buf, 1) == 1 && buf == 'x'); + pthread_join(t, NULL); + + // ppoll should also timeout on an empty pipe + struct timespec ts = {0, 200 * 1000000L}; + struct timespec begin, end; + pfd.revents = 0; + clock_gettime(CLOCK_MONOTONIC, &begin); + assert(ppoll(&pfd, 1, &ts, NULL) == 0); + clock_gettime(CLOCK_MONOTONIC, &end); + long elapsed_ms = (end.tv_sec - begin.tv_sec) * 1000 + + (end.tv_nsec - begin.tv_nsec) / 1000000; + assert(elapsed_ms >= 195); + + printf("done\n"); +} diff --git a/test/test_core.py b/test/test_core.py index 7f2766fb1464e..0289cda28f12e 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9645,6 +9645,12 @@ def test_poll_blocking_asyncify(self): self.skipTest('test requires setTimeout which is not supported under v8') self.do_runf('core/test_poll_blocking_asyncify.c', 'done\n') + @with_asyncify_and_jspi + @requires_pthreads + def test_poll_blocking_asyncify_pthread(self): + self.do_runf('core/test_poll_blocking_asyncify_pthread.c', 'done\n', + cflags=['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME']) + @parameterized({ '': ([],), 'pthread': (['-pthread'],),