Skip to content

Commit 4c3d00b

Browse files
committed
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.
1 parent 312eed6 commit 4c3d00b

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

src/lib/libasync.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,17 @@ addToLibrary({
443443
//
444444
// This is particularly useful for native JS `async` functions where the
445445
// returned value will "just work" and be passed back to C++.
446-
handleAsync: (startAsync) => Asyncify.handleSleep(async (wakeUp) => {
447-
// TODO: add error handling as a second param when handleSleep implements it.
448-
wakeUp(await startAsync());
449-
}),
446+
handleAsync: (startAsync) => {
447+
#if PTHREADS
448+
// When called from a proxied function (PROXY_SYNC_ASYNC), the proxy
449+
// mechanism handles the async return. Skip the Asyncify unwind.
450+
if (PThread.currentProxiedOperationCallerThread) return startAsync();
451+
#endif
452+
return Asyncify.handleSleep(async (wakeUp) => {
453+
// TODO: add error handling as a second param when handleSleep implements it.
454+
wakeUp(await startAsync());
455+
});
456+
},
450457

451458
#elif ASYNCIFY == 2
452459
//
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2026 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#define _GNU_SOURCE
9+
#include <assert.h>
10+
#include <poll.h>
11+
#include <pthread.h>
12+
#include <stdio.h>
13+
#include <time.h>
14+
#include <unistd.h>
15+
16+
static int fds[2];
17+
18+
static void *writer(void *arg) {
19+
write(fds[1], "x", 1);
20+
return NULL;
21+
}
22+
23+
int main(void) {
24+
pthread_t t;
25+
char buf;
26+
struct pollfd pfd = {.events = POLLIN};
27+
28+
pipe(fds);
29+
pfd.fd = fds[0];
30+
31+
// poll should timeout on an empty pipe
32+
assert(poll(&pfd, 1, 100) == 0);
33+
34+
// poll should return immediately when data is already available
35+
write(fds[1], "a", 1);
36+
assert(poll(&pfd, 1, 1000) == 1);
37+
assert(pfd.revents & POLLIN);
38+
assert(read(fds[0], &buf, 1) == 1 && buf == 'a');
39+
40+
// poll should wake up from a cross-thread write
41+
pfd.revents = 0;
42+
pthread_create(&t, NULL, writer, NULL);
43+
assert(poll(&pfd, 1, 5000) == 1);
44+
assert(pfd.revents & POLLIN);
45+
assert(read(fds[0], &buf, 1) == 1 && buf == 'x');
46+
pthread_join(t, NULL);
47+
48+
// ppoll should also timeout on an empty pipe
49+
struct timespec ts = {0, 200 * 1000000L};
50+
struct timespec begin, end;
51+
pfd.revents = 0;
52+
clock_gettime(CLOCK_MONOTONIC, &begin);
53+
assert(ppoll(&pfd, 1, &ts, NULL) == 0);
54+
clock_gettime(CLOCK_MONOTONIC, &end);
55+
long elapsed_ms = (end.tv_sec - begin.tv_sec) * 1000 +
56+
(end.tv_nsec - begin.tv_nsec) / 1000000;
57+
assert(elapsed_ms >= 195);
58+
59+
printf("done\n");
60+
}

test/test_core.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9645,6 +9645,18 @@ def test_poll_blocking_asyncify(self):
96459645
self.skipTest('test requires setTimeout which is not supported under v8')
96469646
self.do_runf('core/test_poll_blocking_asyncify.c', 'done\n')
96479647

9648+
@no_esm_integration('WASM_ESM_INTEGRATION is not compatible with ASYNCIFY=1')
9649+
@requires_pthreads
9650+
def test_poll_blocking_asyncify_pthread(self):
9651+
# Only testing ASYNCIFY=1: JSPI's handleAsync is a plain async function
9652+
# and doesn't have this bug. Also, with_asyncify_and_jspi can't be
9653+
# combined with requires_pthreads since require_jspi may select d8 which
9654+
# doesn't support pthreads (require_pthreads then hard-fails instead of
9655+
# skipping).
9656+
self.set_setting('ASYNCIFY')
9657+
self.do_runf('core/test_poll_blocking_asyncify_pthread.c', 'done\n',
9658+
cflags=['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'])
9659+
96489660
@parameterized({
96499661
'': ([],),
96509662
'pthread': (['-pthread'],),

0 commit comments

Comments
 (0)