async JavaScript code not running in USE_PTHREADS build

37 views
Skip to first unread message

Kevin McGrath

unread,
Mar 25, 2020, 4:56:34 PM3/25/20
to emscripten-discuss
Hello!

I'm hoping someone has already run into this before and can tell me what I'm doing wrong...

I have a JavaScript function I'm calling from a C++ multi-threaded game that is designed to perform an async HTTP fetch.

I'm building the game using USE_PTHREADS=1 and PROXY_TO_PTHREAD=1 and I've tried both having the JavaScript function embedded in a EM_JS() and in a --js-library.  The C++ code sparks off a pthread and calls into the JavaScript function, and that JavaScript code executes and then returns back to the C++ code.  What doesn't work is any of the Promise / await code, nothing is executing in those code blocks.

I've tried calling emscripten_thread_sleep() from the C++ code, which is still executing in the thread waiting on the JavaScript asynchronous code to call it back, it just never happens.  The network request is just marked as "(pending)" in Chrome and never changes.

So my question is, what's the right way to run Promise/async/await code in a WebAssembly pthread/Web Worker?

Thank you!!

Alon Zakai

unread,
Mar 25, 2020, 7:39:29 PM3/25/20
to emscripte...@googlegroups.com
emscripten_thread_sleep() will check the pthread event queue, which means it can receive C messages from other threads - those are synchronized using shared memory. However, it can't help with JS events, as to get those to happen you need to actually return to the main JS event loop. The browser won't run those events otherwise.

To do that, you can use Asyncify (https://emscripten.org/docs/porting/asyncify.html) and emscripten_sleep(). Or, you can manually return to the main event loop yourself, replacing an infinite event loop with an emscripten_set_main_loop() etc.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/emscripten-discuss/aecd43c5-6662-4d7d-bb8c-15461e75522e%40googlegroups.com.
Message has been deleted

Kevin McGrath

unread,
Mar 25, 2020, 8:52:51 PM3/25/20
to emscripten-discuss
  Ah!  Thanks for the details!  I looked into Asyncify and ruled it out because of this from the emscripten documentation:

It is not safe to start an async operation while another is already running. The first must complete before the second begins.

  That would mean one HTTP request at a time, one after another, and any time we want to use Asyncify for any other async JavaScript tasks we may run into trouble that would be a nightmare to debug.  Plus there's more issues described in that doc, such as if we get an event (like a keypress) while the async JavaScript is running it doesn't sound too stable when it calls into compiled code.  Lastly, they mention that you should always run optimized builds if you're using Asyncify, which will not be fun to debug.  Taking all of that into consideration, it didn't sound like a good option to me, especially since I wasn't sure it would work in the first place with USE_PTHREADS=1.

  I'm not sure I understand how emscripten_set_main_loop() will help my JavaScript execute the async code... are you saying that the compiled code in each thread is blocking the JavaScript code?

  Sorry for so many questions, I'm very new to JavaScript and know very little about its internal workings.  Also seems impossible to Google for this kind of stuff.

  Thank you!

J Decker

unread,
Mar 26, 2020, 4:22:59 AM3/26/20
to emscripten-discuss
Not sure if this relates... I had a very similar issue with a node addon, and promises just not resolving.  I'm not sure specifially how your browsers Promises get scheduled, but in V8, the library schedules them as microtasks, that you basically have to call process.tick() in order to kick that event loop.  I don't know if the same functionality exists in the JS browser... 
You're not supposed to have to worry about such details, but in a thread, if I didn't also call the runmicrotasks sort of thing, the promises resolved never got dispatched.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.

Kevin McGrath

unread,
Mar 26, 2020, 1:18:50 PM3/26/20
to emscripten-discuss
I'm not sure the browsers expose anything to us mere mortals to allow us to tick the microtasks.  :(  I can't find anything like process.nextTick() or a way to force the Promise I get back from fetch to resolve/execute.

I'd like to get a better understanding of how pthreads work in WebAssembly.  I understand they are Web Workers that use SharedArray, but won't a Web Worker process its microtasks?  What makes whatever emscripten is doing break that?

What's frustrating is that my only option seems to be to rework this using synchronous XHR, even though that seems to be discouraged among JavaScript developers.  I'm just not sure how else I can make my HTTP requests work without blocking the main browser thread.


On Thursday, March 26, 2020 at 1:22:59 AM UTC-7, J Decker wrote:
Not sure if this relates... I had a very similar issue with a node addon, and promises just not resolving.  I'm not sure specifially how your browsers Promises get scheduled, but in V8, the library schedules them as microtasks, that you basically have to call process.tick() in order to kick that event loop.  I don't know if the same functionality exists in the JS browser... 
You're not supposed to have to worry about such details, but in a thread, if I didn't also call the runmicrotasks sort of thing, the promises resolved never got dispatched.

On Wed, Mar 25, 2020 at 5:52 PM Kevin McGrath wrote:
  Ah!  Thanks for the details!  I looked into Asyncify and ruled it out because of this from the emscripten documentation:

It is not safe to start an async operation while another is already running. The first must complete before the second begins.

  That would mean one HTTP request at a time, one after another, and any time we want to use Asyncify for any other async JavaScript tasks we may run into trouble that would be a nightmare to debug.  Plus there's more issues described in that doc, such as if we get an event (like a keypress) while the async JavaScript is running it doesn't sound too stable when it calls into compiled code.  Lastly, they mention that you should always run optimized builds if you're using Asyncify, which will not be fun to debug.  Taking all of that into consideration, it didn't sound like a good option to me, especially since I wasn't sure it would work in the first place with USE_PTHREADS=1.

  I'm not sure I understand how emscripten_set_main_loop() will help my JavaScript execute the async code... are you saying that the compiled code in each thread is blocking the JavaScript code?

  Sorry for so many questions, I'm very new to JavaScript and know very little about its internal workings.  Also seems impossible to Google for this kind of stuff.

  Thank you!

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

Alon Zakai

unread,
Mar 26, 2020, 2:24:39 PM3/26/20
to emscripte...@googlegroups.com
> I'm not sure I understand how emscripten_set_main_loop() will help my JavaScript execute the async code... are you saying that the compiled code in each thread is blocking the JavaScript code?

Yes, see


if you have a "while (1)" loop as in the example there, then it never exits back to the JS event loop. All execution has to stop for that to happen. That's the same on the main thread and on workers, and the solution can be the same, as described there.

About Asyncify: I think most of your worries are not an issue (it works with pthreads, and you can implement multiple async events at once, but you need to add a little JS layer yourself to multiplex them, etc.). But if you can just switch to using emscripten_set_main_loop() or some other async way of doing your main loop, that would be much simpler and much better.



On Wed, Mar 25, 2020 at 5:51 PM Kevin McGrath <kevin....@catdaddy.com> wrote:
  Ah!  Thanks for the details!  I looked into Asyncify and ruled it out because of this from the emscripten documentation:

It is not safe to start an async operation while another is already running. The first must complete before the second begins.

  That would mean one HTTP request at a time, one after another, and any time we want to use Asyncify for any other async JavaScript tasks we may run into trouble that would be a nightmare to debug.  Plus there's more issues described in that doc, such as if we get an event (like a keypress) while the async JavaScript is running it doesn't sound too stable when it calls into compiled code.  Lastly, they mention that you should always run optimized builds if you're using Asyncify, which will not be fun to debug.  Taking all of that into consideration, it didn't sound like a good option to me, especially since I wasn't sure it would work in the first place with USE_PTHREADS=1.

  I'm not sure I understand how emscripten_set_main_loop() will help my JavaScript execute the async code... are you saying that the compiled code in each thread is blocking the JavaScript code?

  Sorry for so many questions, I'm very new to JavaScript and know very little about its internal workings.  Also seems impossible to Google for this kind of stuff.

  Thank you!


Kevin McGrath

unread,
Mar 26, 2020, 3:30:44 PM3/26/20
to emscripten-discuss
Well, in my C++ pthread tick function I tried performing a call to emscripten_cancel_main_loop() and then called emscripten_set_main_loop_arg(WasmHTTPRequestMainLoop, this, 10, 0), however my new main loop for this thread is never called, and my pthread tick function kept ticking.

I would expect my new C main loop function to take over for my old pthread C++ main loop, but in fact nothing happened.  It just kept calling my original pthread C++ tick function.

I don't see any example code in the emscripten SDK that covers pthreads and asynchronous JavaScript, so I doubt this is something that's even tested, which seems unfortunate.

Again, Asyncify doesn't sound like a solution, especially not if I have to write some JavaScript system to multiplex N number of asynchronous JavaScript functions.  It'll bloat the game code, or make debugging impossible, and I still don't get truly asynchronous JavaScript out of it (I would consider one function running at a time on a multi-core device to be pseudo-asynchronous anyway).

It's quickly looking like right now we can't have asynchronous JavaScript code if we use pthreads.  :(
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

Alon Zakai

unread,
Mar 26, 2020, 5:53:49 PM3/26/20
to emscripte...@googlegroups.com
> I don't see any example code in the emscripten SDK that covers pthreads and asynchronous JavaScript, so I doubt this is something that's even tested, which seems unfortunate.

We do have tests on this, and they pass. For example, browser.test_emscripten_main_loop tests the main loop with PROXY_TO_PTHREAD (which means that the C main() function runs in a pthread), and a bunch of other tests cover other async operations.

It is possible you've found an unknown bug. If so please reduce it to a small testcase and submit that.

- Alon


To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/emscripten-discuss/d4918085-f6bc-4912-94a1-3412763525fe%40googlegroups.com.

Kevin McGrath

unread,
Mar 26, 2020, 7:52:11 PM3/26/20
to emscripten-discuss
I searched through the browser tests in the SDK, there's no JavaScript code that uses async/await or Promises that I could see, but there is JavaScript code that uses Asyncify.

I'll try to work up a small test case and if I can still reproduce it with that case I'll file a bug so it can be tracked or at least discussed.

Thank you very much for all of your help and advice.  I might not have a solution yet, but it has gotten me farther along I believe.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

Floh

unread,
Mar 27, 2020, 10:10:39 AM3/27/20
to emscripten-discuss
I have an example of JS promises (resolved to a regular callback, so not async/await) here in my WIP WebGPU initialization code. It's running on the main thread though without blocking though, so I'm not sure if this fits into your "application model".

The idea is that the C side calls a JS function which spawns some async work and returns immediately to the C side. Then when the async work has finished, the JS code calls a C function to pass results back:


The JS functions requestAdapter(), requestDevice() and getSwapChainPreferredFormat() return a JS promise which is resolved into a callback via then().
Reply all
Reply to author
Forward
0 new messages