Repository navigation
Async JS library funcs should not be marked for Asyncify transform #20289
Copy link
Copy link
Closed
Description
After #19093 any async function () { } in JS libraries is marked for Asyncify transform. It's a bit hard to describe why, but I don't think this is correct:
- For Asyncify (
-s ASYNCIFY), functions must be wrapped intoAsyncify.handleAsyncto work correctly, which would make them not recognised by this check. And, vice versa, if an unwrappedasync function () {}is recognised by this check, as it is now, it will be passed to Binaryen and transformed - which expands the file size - for no reason, since it's going to be called only directly and not insideAsyncify.handleAsync/Asyncify.handleSleep. - For JSPI, we can sort of get away with it since it's much less reliant on wrapping, so it at least seems to work, but since such functions are still not wrapped into
handleAsync, they don't get the runtime keepalive counter increased/decreased upon entering/exit correspondingly. - For
PROXY_TO_PTHREADmode, such functions need to be treated asvoidas async wakeup is happening manually viaproxying.hfunctions. Which means that again, they shouldn't be marked as Asyncify functions.
For example, looking at current usages in
emscripten/src/library_wasmfs_jsimpl.js
Lines 92 to 99 in 992d1e9
| _wasmfs_jsimpl_async_read: async function(ctx, backend, file, buffer, length, offset, result_p) { | |
| #if ASSERTIONS | |
| assert(wasmFS$backends[backend]); | |
| #endif | |
| var result = await wasmFS$backends[backend].read(file, buffer, length, offset); | |
| {{{ makeSetValue('result_p', 0, 'result', SIZE_TYPE) }}}; | |
| _emscripten_proxy_finish(ctx); | |
| }, |
-s ASYNCIFY -s PROXY_TO_PTHREAD (because their project depends on both of those features), right now this function will be passed to and transformed by Binaryen as Asyncify function, increasing the Wasm size, but then never used as Asyncify function because it's not wrapped into Asyncify.handleAsync.
This seems like a footgun.
I'd suggest that we should either 1) require users to explicitly mark functions as __async: true, which is easy to put into #ifdef so that it only activates when the function is really meant to be used with Asyncify or 2) provide some more magic & automatic wrapping for all async functions that would perform proxying.h-based wakeup in PROXY_TO_PTHREAD mode and Asyncify-based wakeup otherwise.
cc @brendandahl
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
brendandahl commentedon Sep 19, 2023
We've talked a bit about automatically wrapping async functions to do
Asyncify.handleAsyncso the user doesn't have to and only using promises, but there could be some potential downsides(mentioned in link above). Overall, it would be nice to unify JSPI/Asyncify and make it less error prone (e.g. a user forgetting handleAsync).As for asyncify+proxy, I hadn't really thought of this mix. In an ideal world, proxying would just use JSPI, but that is probably a pretty far ways off. Another options is async proxy functions could opt-out of asyncify with
__async: false.sbc100 commentedon Sep 19, 2023
I'm not sure about that.. I think it might be faster/better to just block the calling thread like we do today rather than returning to the event loop (at least in most cases).
brendandahl commentedon Sep 19, 2023
I think we discussed this in a meeting awhile ago. IIRC, at least for the web, any api that is async would need to do async work and need to return the event loop no matter what, so it wouldn't really affect performance. I could see the use case sync/async mixing for something like reading some buffer and sometimes it would be sync and sometimes it would be async if the buffer ran out.
Alternatively, we could always wrap and use promises (the common case), but provide a mechanism to do sync work. Now that I think of it, this mechanism exists today, a user could have an async and sync version of a function if they really wanted.
RReverser commentedon Sep 20, 2023
Unifying
PROXY_TO_PTHREAD+ Asyncify via single API for the user is something I also thought about in the past, and would love to see it happen someday, but it's clear it requires a bigger design discussion / decision.Meanwhile, specifically for this issue, could we just not mark
asyncfunctions as Asyncify since they're not really Asyncify-compatible without the explicit wrapper in today's implementation?Until there is clear design on automatic wrapping, I think the current implicit marking w/o wrapping can cause more non-obvious problems than asking user to explicitly mark functions as
__async: truewhen necessary.RReverser commentedon Sep 20, 2023
For this just want to add is that what @sbc100 meant is that the calling thread (aka non-main thread) doesn't have to return to event loop in that scenario. When proxying, the main thread indeed needs to return to event loop to execute the actual async work, but the extra pthread that called it can just wait synchronously on an atomic - there's no need to unwind both of them.
brendandahl commentedon Sep 20, 2023
I misread that. Yeah, no need to unwind in the proxying case.
brendandahl commentedon Apr 12, 2024
A little update...
It's looking like JSPI will be changing to allow Wasm imports to return a value or promise. This is because always returning a promise can lead to performance issues when the import doesn't need to actually suspend. This will align more asyncify=1 and make it so we should support sync returns with JSPI.
My initial thoughts are doing something like the following where we try to get rid of the user having to do handleAsync/handleSleep and separate out the
asyncfunction and how asyncify handles it.brendandahl commentedon Feb 4, 2025
Another update here, JSPI is changing again. JSPI wrapped imports will now always suspend (i.e. use Promise.resolve path), so there's not really a point in supporting the synchronous return path anymore. I think going forward now we should continue to use the
myFoo__async: trueto mark JSPI functions and stop auto JSPI'ingasyncfunctions.[JSPI] Do not automatically wrap `async` library functions.
[JSPI] Do not automatically wrap `async` library functions.
[JSPI] Do not automatically wrap `async` library functions. (#24550)
sbc100 commentedon Dec 31, 2025
I've opened #26026 which enabled the auto-wrapping
handleAsync. Hopefully this will mean that eventually nobody will need to writehandleAsyncexplictly at all.