Skip to content

[JSPI] Pause / resume main loop before executing an async functions (matching behavior of ASYNCIFY=1)#26249

Open
inolen wants to merge 1 commit intoemscripten-core:mainfrom
inolen:inolen/jspi_suspend_main_loop
Open

[JSPI] Pause / resume main loop before executing an async functions (matching behavior of ASYNCIFY=1)#26249
inolen wants to merge 1 commit intoemscripten-core:mainfrom
inolen:inolen/jspi_suspend_main_loop

Conversation

@inolen
Copy link
Collaborator

@inolen inolen commented Feb 12, 2026

Seems straight forward, but would appreciate someone more familiar with this code sanity checking.

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

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

Seems reasonable yes. Maybe we can write a test for this the runs in both modes?


if (typeof MainLoop != 'undefined' && MainLoop.func) {
MainLoop.resume();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if we should put these 2 lines in the finally block? Then we don't need t new res variable I think.

@inolen inolen force-pushed the inolen/jspi_suspend_main_loop branch from de8611d to 310fa87 Compare February 12, 2026 13:40
@inolen
Copy link
Collaborator Author

inolen commented Feb 12, 2026

Added a JSPI specific test, I'm not quite sure the best way to have that run for both modes.

Hate adding tests with sleeps, but not sure of a better way to test that here.

'-Wno-experimental',
'--post-js=post.js'])

@requires_jspi
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can use @with_asyncify_and_jspi here to get both modes.

Then maybe the test itself should be called test_asyncify_mainloop?

}

int main() {
emscripten_set_main_loop( loop, 1000, 1 );
Copy link
Collaborator

Choose a reason for hiding this comment

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

No space inside braces.

@inolen
Copy link
Collaborator Author

inolen commented Feb 12, 2026

Just realized this is failing because of a change I didn't commit, but I'm not sure if you want this right now -

diff --git a/src/lib/libeventloop.js b/src/lib/libeventloop.js
index ad59b9262..3646198a2 100644
--- a/src/lib/libeventloop.js
+++ b/src/lib/libeventloop.js
@@ -378,6 +378,9 @@ LibraryJSEventLoop = {
   emscripten_set_main_loop__deps: ['$setMainLoop'],
   emscripten_set_main_loop: (func, fps, simulateInfiniteLoop) => {
     var iterFunc = {{{ makeDynCall('v', 'func') }}};
+#if JSPI
+    iterFunc = WebAssembly.promising(iterFunc);
+#endif
     setMainLoop(iterFunc, fps, simulateInfiniteLoop);
   },

I believe I had read some chatter here somewhere about automating this vs sprinkling it around everywhere.

@sbc100
Copy link
Collaborator

sbc100 commented Feb 12, 2026

Just realized this is failing because of a change I didn't commit, but I'm not sure if you want this right now -

diff --git a/src/lib/libeventloop.js b/src/lib/libeventloop.js
index ad59b9262..3646198a2 100644
--- a/src/lib/libeventloop.js
+++ b/src/lib/libeventloop.js
@@ -378,6 +378,9 @@ LibraryJSEventLoop = {
   emscripten_set_main_loop__deps: ['$setMainLoop'],
   emscripten_set_main_loop: (func, fps, simulateInfiniteLoop) => {
     var iterFunc = {{{ makeDynCall('v', 'func') }}};
+#if JSPI
+    iterFunc = WebAssembly.promising(iterFunc);
+#endif
     setMainLoop(iterFunc, fps, simulateInfiniteLoop);
   },

I believe I had read some chatter here somewhere about automating this vs sprinkling it around everywhere.

Isn't the solution here to make sure the interFunc is in JSPI_EXPORTS? @brendandahl

@inolen inolen force-pushed the inolen/jspi_suspend_main_loop branch from 310fa87 to 8249562 Compare February 12, 2026 17:33
@inolen
Copy link
Collaborator Author

inolen commented Feb 12, 2026

Hmm, I tried adding a '-sJSPI_EXPORTS=loop' into the cflags variable there, but I can't get that function to show up in the exports array passed to instrumentWasmExports.

Ah, that finally worked if I also added '-sEXPORTED_FUNCTIONS=_main,_loop'.

@inolen inolen force-pushed the inolen/jspi_suspend_main_loop branch from 8249562 to 5e1f1e5 Compare February 12, 2026 18:42
@sbc100 sbc100 changed the title libasync / mimic behavior of ASYNCIFY=1 when using JSPI and pause / r… Feb 12, 2026
@sbc100 sbc100 enabled auto-merge (squash) February 12, 2026 19:15
@inolen inolen force-pushed the inolen/jspi_suspend_main_loop branch from 5e1f1e5 to ba350b7 Compare February 13, 2026 12:57
…esume main loop before executing an async function
@inolen inolen force-pushed the inolen/jspi_suspend_main_loop branch from ba350b7 to b6fbd3d Compare February 14, 2026 19:09
@inolen
Copy link
Collaborator Author

inolen commented Feb 14, 2026

Oops, somehow had rebased on an ancient branch, fixed now.

@inolen
Copy link
Collaborator Author

inolen commented Feb 15, 2026

Adding a note here - Asyncify.handleAsync also needs to be called for all async syscalls.

Without doing so, they run into the same problem that's tested here when using EM_ASYNC_JS - multiple frames can incorrectly pile up if the syscall waits.

@sbc100
Copy link
Collaborator

sbc100 commented Feb 16, 2026

Adding a note here - Asyncify.handleAsync also needs to be called for all async syscalls.

Without doing so, they run into the same problem that's tested here when using EM_ASYNC_JS - multiple frames can incorrectly pile up if the syscall waits.

EM_ASYNC_JS automatically adds Asyncify.handleAsync, right?

As does __async: auto. What other syscalls are you referring too that are async but don't contains Asyncify.handleAsync?

@inolen
Copy link
Collaborator Author

inolen commented Feb 16, 2026

EM_ASYNC_JS automatically adds Asyncify.handleAsync, right?

Yep.

As does __async: auto. What other syscalls are you referring too that are async but don't contains Asyncify.handleAsync?

Does __async: auto wrap the syscall in Asyncify.handleAsync? The branch I was working with was a month or so old (no async: auto, just async: true iirc), and at least select() was not being automatically wrapped (and I didn't see code for doing that). Edit: Looks like yes it does as of bc217ce, so that note can be ignored :)

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

Labels

None yet

2 participants