Skip to content

Commit e414ec8

Browse files
committed
Fix Asyncify.handleAsync conflict with PROXY_SYNC_ASYNC
When a JS library function has both __proxy:'sync' and __async:'auto', the compiler generates an Asyncify.handleAsync wrapper. When called from the PROXY_SYNC_ASYNC path on the main thread, handleAsync triggers an Asyncify unwind instead of returning a Promise, causing "rtn.then is not a function" in the proxy infrastructure. Fix by generating a PThread.currentProxiedOperationCallerThread check in handleAsyncFunction (jsifier.mjs): when in a proxied context, call the inner function directly and skip the Asyncify unwind, letting the proxy mechanism handle the async return.
1 parent 312eed6 commit e414ec8

File tree

2 files changed

+29
-4
lines changed

2 files changed

+29
-4
lines changed

src/jsifier.mjs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,16 +360,29 @@ ${body};
360360
});
361361
}
362362

363-
function handleAsyncFunction(snippet, sig) {
363+
function handleAsyncFunction(snippet, sig, proxyingMode) {
364364
const return64 = sig && (MEMORY64 && sig.startsWith('p') || sig.startsWith('j'))
365365
let handleAsync = 'Asyncify.handleAsync(innerFunc)'
366366
if (return64 && ASYNCIFY == 1) {
367367
handleAsync = makeReturn64(handleAsync);
368368
}
369+
// When a function uses both __proxy:'sync' and __async:'auto', the proxy
370+
// mechanism (PROXY_SYNC_ASYNC) handles the async return itself. In that
371+
// case, skip the Asyncify unwind and call the inner function directly so
372+
// the proxy can use .then() on the returned Promise.
373+
const skipHandleAsync = PTHREADS && ASYNCIFY == 1 && proxyingMode === 'sync';
369374
return modifyJSFunction(snippet, (args, body, async_, oneliner) => {
370375
if (!oneliner) {
371376
body = `{\n${body}\n}`;
372377
}
378+
if (skipHandleAsync) {
379+
return `\
380+
function(${args}) {
381+
let innerFunc = async () => ${body};
382+
if (PThread.currentProxiedOperationCallerThread) return innerFunc();
383+
return ${handleAsync};
384+
}\n`;
385+
}
373386
return `\
374387
function(${args}) {
375388
let innerFunc = ${async_} () => ${body};
@@ -474,11 +487,11 @@ function(${args}) {
474487
compileTimeContext.i53ConversionDeps.forEach((d) => deps.push(d));
475488
}
476489

490+
const proxyingMode = LibraryManager.library[symbol + '__proxy'];
491+
477492
if (ASYNCIFY && isAsyncFunction == 'auto') {
478-
snippet = handleAsyncFunction(snippet, sig);
493+
snippet = handleAsyncFunction(snippet, sig, proxyingMode);
479494
}
480-
481-
const proxyingMode = LibraryManager.library[symbol + '__proxy'];
482495
if (proxyingMode) {
483496
if (!['sync', 'async', 'none'].includes(proxyingMode)) {
484497
error(`JS library error: invalid proxying mode '${symbol}__proxy: ${proxyingMode}' specified`);

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.c', 'done\n',
9658+
cflags=['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'])
9659+
96489660
@parameterized({
96499661
'': ([],),
96509662
'pthread': (['-pthread'],),

0 commit comments

Comments
 (0)