Skip to content

Add __async: 'proxy' for functions that block only when proxied from a pthread - #28094

Open
guybedford wants to merge 3 commits into
emscripten-core:mainfrom
guybedford:async-proxy
Open

guybedford wants to merge 3 commits into
emscripten-core:mainfrom
guybedford:async-proxy

Conversation

@guybedford

Copy link
Copy Markdown
Collaborator

This adds __async: 'proxy' for a JS library function that must block when called from a pthread but can never be a suspending import, replacing #28093.

write() on a pipe is the case: a pthread writing to a full pipe has to wait for the reader, which the main thread can do on its behalf by returning a Promise from the sync-proxied call (#28083). But fd_write sits behind every printf, so marking it __async: 'auto' would make it a suspending import under ASYNCIFY/JSPI, instrumenting or trapping every caller. #28083 therefore guards the marker with #if PTHREADS && !ASYNCIFY and loses the pthread wait in ASYNCIFY/JSPI builds.

'proxy' is that contract spelled out: the function stays a plain import in every build, and its sync argument (as for 'auto', #28022) is false only on behalf of a sync-proxied pthread caller.

fd_write__async: 'proxy',
fd_write: (fd, iov, iovcnt, pnum, sync) => { ... },
  • A 'proxy' function is never added to the suspending imports and never wrapped in handleAsync; its proxied calls use the Promise-capable sync proxying mode, like 'auto'.
  • sync is true under ASYNCIFY/JSPI too, since the import cannot suspend.
  • __async: 'proxy' without __proxy: 'sync' is a build error, as the function would then have no asynchronous path.
  • Docs are added next to the sync description in the "Marking JS library functions as async" section.

Tests: jslib.test_jslib_async_auto_sync gains 'proxy' variants in plain, ASYNCIFY, JSPI and PROXY_TO_PTHREAD builds, and jslib.test_jslib_async_proxy_requires_proxying covers the error.

Made with AI assistance under my review

@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 the "__async: 'proxy'" configuration for JS library functions, which are designed to block when called from a pthread but should not be treated as suspending imports under ASYNCIFY/JSPI. It updates jsifier.mjs to handle this new mode, ensures it requires "__proxy: 'sync'", and adds corresponding tests. A critical review comment points out that renaming isAsyncFunction to asyncMode in jsifier.mjs leaves isAsyncFunction undefined later in the scope, which would cause a runtime ReferenceError during compilation with ASYNCIFY.

Comment thread src/jsifier.mjs Resolved
@PedroRosalba

Copy link
Copy Markdown

A body that ignores sync and returns a Promise where the caller can't wait comes back as 0, with no assertion even under -sASSERTIONS. That's already the case for 'auto' without ASYNCIFY, but 'proxy' makes it reachable in ASYNCIFY/JSPI builds too, where 'auto' would have suspended.

What do you think of having the wrapper recover the value instead, now that #28083 has SyncDeferred? When sync is true and the body returns a thenable, the wrapper would call its then and use the value if the callback ran right away, which it does for a SyncDeferred that has already settled. Only a thenable still pending at that point would be an error, which an ASSERTIONS check could report. A real Promise would also land there, since its callbacks always wait for a microtask. If that direction works for you, I'm happy to try it; otherwise the assertion alone would already catch the silent 0.

Separately, none of the test_jslib_async_auto_sync variants combines pthreads with ASYNCIFY/JSPI, which is the build 'proxy' is for. -pthread -sPROXY_TO_PTHREAD -sASYNCIFY and -sJSPI variants pass for me with the 6.0.13 LLVM. While adding them I noticed the test's #if defined(__EMSCRIPTEN_PTHREADS__) expects 42 in any pthreads build, but with plain -pthread the call is on the main thread and -1 is correct. It holds today only because every pthread variant also uses PROXY_TO_PTHREAD.

AI-generated with Claude, reviewed by me before posting.

@guybedford

Copy link
Copy Markdown
Collaborator Author

Thanks @PedroRosalba for checking this, all three points taken.

Under ASSERTIONS a body that returns a Promise where sync is true now fails with a message naming the function. I went with the assertion only rather than unwrapping an already-settled thenable: when sync is true the body has to return the value, and a settled thenable there means it could have, so recovering would just hide the bug.

Also added the proxy_pthread_asyncify and proxy_pthread_jspi variants. The test now keys the waiting case on an explicit WAITS define passed by the PROXY_TO_PTHREAD variants rather than on __EMSCRIPTEN_PTHREADS__, with plain -pthread variants checking that the main thread gets -1.

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.

2 participants