Skip to content

Pass sync to __async: 'auto' library functions - #28022

Open
guybedford wants to merge 1 commit into
mainfrom
gbedford/async-sync-arg
Open

guybedford wants to merge 1 commit into
mainfrom
gbedford/async-sync-arg

Conversation

@guybedford

Copy link
Copy Markdown
Collaborator

This lets an __async: 'auto' JS library function know whether its caller can wait for a returned Promise, so one body can block where the stack allows it and fall back to a synchronous result (typically an EAGAIN-style error) where it doesn't.

Whether the caller can wait is a per-call property: under PTHREADS it depends on whether the body is running on behalf of a sync-proxied pthread or directly on the main thread. __syscall_poll and epoll_pwait each computed this by hand from PThread.currentProxiedOperationCallerThread, and the upcoming blocking socket calls need the same. So it is now passed as an argument:

lookup__sig: 'pp',
lookup__async: 'auto',
lookup__proxy: 'sync',
lookup: (name, sync) => sync ? -EAGAIN : fetchSomething(name),
  • A body whose last parameter, following those of its __sig, is named sync receives there whether it must complete synchronously: false under ASYNCIFY/JSPI and on behalf of a sync-proxied pthread caller, true otherwise. Bodies not naming it are unchanged.
  • Naming sync at the wrong position (e.g. omitting unused trailing __sig parameters) is a compile error, rather than silently receiving another argument.
  • An 'auto' body returning a Promise without ASYNCIFY (on a pthread's behalf) now has its p/j return conversion applied to the resolved value under wasm64, and the proxied result is converted from BigInt on the sync-async path too.
  • __syscall_poll and epoll_pwait take sync instead of computing it.
  • Docs are added to the existing "Marking JS library functions as async" section, including that under ASYNCIFY/JSPI the caller must still be on a stack that can suspend.

Tests: jslib.test_jslib_async_auto_sync with pointer-returning and void functions in plain, ASYNCIFY, JSPI and PROXY_TO_PTHREAD builds, wasm32 and wasm64, and jslib.test_jslib_async_auto_sync_arity for the misplaced parameter error.

Made with AI assistance under my review

An 'auto' body whose last parameter, following those of its __sig, is
named `sync` receives there whether it must complete synchronously on this
call, as a returned Promise could not be waited for: never under
ASYNCIFY/JSPI, nor on behalf of a sync-proxied pthread caller, which awaits
it; otherwise it is. __syscall_poll and epoll_pwait, which computed this by
hand, now take it.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for __async: 'auto' library functions to conditionally execute synchronously or asynchronously using a new sync parameter. This parameter is automatically passed to the JS library function based on whether the calling context can suspend (under ASYNCIFY/JSPI) or wait (proxied pthreads). The review feedback points out a critical issue where a synchronous proxied call (where ctx is falsy) might incorrectly evaluate sync as false because PThread.currentProxiedOperationCallerThread is truthy, leading to an unawaited Promise. The reviewer suggests tracking whether the proxied operation can actually wait via a new property PThread.currentProxiedOperationCanWait and using it to determine the sync parameter.

Comment thread src/lib/libpthread.js
Comment on lines 1085 to 1087
PThread.currentProxiedOperationCallerThread = callingThread;
var rtn = func(...proxiedJSCallArgs);
PThread.currentProxiedOperationCallerThread = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If a synchronous proxied call (which does not support async, i.e., ctx is falsy) calls an __async: 'auto' function, PThread.currentProxiedOperationCallerThread will still be truthy. This causes sync to be evaluated as false, meaning the function will return a Promise. However, since ctx is falsy, the Promise is never awaited, and the pthread receives a Promise object (coerced to a garbage value) instead of the resolved value.

To fix this, we should track whether the current proxied operation can actually wait (i.e., whether ctx is present) and use that to determine the sync parameter.

    PThread.currentProxiedOperationCallerThread = callingThread;
    PThread.currentProxiedOperationCanWait = !!ctx;
    var rtn = func(...proxiedJSCallArgs);
    PThread.currentProxiedOperationCallerThread = 0;
    PThread.currentProxiedOperationCanWait = false;

@guybedford guybedford Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All __async pthreads function calls across threads are async, so it is only the same-thread calls (to non-proxied __async functions, or from the main thread) which need to set sync: true, and currentProxiedOperationCallerThread exhaustively handles this without needing a separate ctx flag. Under JSPI and asyncify, sync is always false.

Comment thread src/jsifier.mjs
@guybedford
guybedford added this pull request to stack #28023 October 20, 2026 04:34
@guybedford
guybedford removed this pull request from stack #28023 October 20, 2026 04:42
@guybedford
guybedford added this pull request to stack #28024 October 20, 2026 04:42

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant