Skip to content

Commit b85c756

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 4d5a748 commit b85c756

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9638,6 +9638,11 @@ def test_poll_blocking_asyncify(self):
96389638
self.skipTest('test requires setTimeout which is not supported under v8')
96399639
self.do_runf('core/test_poll_blocking_asyncify.c', 'done\n')
96409640

9641+
def test_poll_blocking_asyncify_pthread(self):
9642+
self.require_pthreads()
9643+
self.do_runf('core/test_poll_blocking_asyncify_pthread.c', 'done\n',
9644+
cflags=['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME', '-sASYNCIFY'])
9645+
96419646
@parameterized({
96429647
'': ([],),
96439648
'pthread': (['-pthread'],),

0 commit comments

Comments
 (0)