Skip to content

Multithreaded Callbacks #24249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ErikSom opened this issue May 3, 2025 · 4 comments
Open

Multithreaded Callbacks #24249

ErikSom opened this issue May 3, 2025 · 4 comments
Labels

Comments

@ErikSom
Copy link

ErikSom commented May 3, 2025

I'm working on the WebAssembly port of Box2D 3, which you can find here: box2d3-wasm.

We're aiming to make it feature-complete and highly flexible for web users. One challenge I'm currently facing involves supporting the PreSolve callback in JavaScript.

Ideally, I want developers using the WASM build to be able to define a PreSolve callback directly in JavaScript, so they can make custom, real-time decisions on contact resolution.

Here's how I've set it up currently:
https://github.com/Birch-san/box2d3-wasm/blob/0a839337b041e1de851fc7be9e9a6aae6e4edc4e/box2d3-wasm/csrc/glue.cpp#L1562

This approach works perfectly in single-threaded builds and behaves exactly as intended. However, in multithreaded builds, I get the following runtime error:

Assertion failed: pthread_equal(thread, pthread_self()) && "val accessed from wrong thread"

Which makes sense, the callback is defined in JS, and Emscripten's val interface enforces that JavaScript calls only occur on the main thread for safety.

My question is: is there a known or recommended pattern for allowing JS-defined callbacks (like PreSolve) in a multithreaded Emscripten environment? Or is the best route simply to restrict this feature to single-threaded builds? ( I tinkered with proxying, but this resulted in a deadlock for me)

Any insights, suggestions, or pointers would be greatly appreciated!

@RReverser
Copy link
Collaborator

My question is: is there a known or recommended pattern for allowing JS-defined callbacks (like PreSolve) in a multithreaded Emscripten environment? Or is the best route simply to restrict this feature to single-threaded builds? ( I tinkered with proxying, but this resulted in a deadlock for me)

Proxying API is what you should use if you want to invoke callbacks on different threads, yes.

Emscripten's val interface enforces that JavaScript calls only occur on the main thread for safety.

Note that this is not entirely correct: it enforces that JavaScript calls occur on the same thread as where the JavaScript callback is defined, because JS itself doesn't allow you to share raw JS values (including callbacks) between Workers and main thread. But "the same thread" doesn't necessarily have to mean "main thread", you might have callbacks defined in workers too.

@ErikSom
Copy link
Author

ErikSom commented May 5, 2025

Note that this is not entirely correct: it enforces that JavaScript calls occur on the same thread as where the JavaScript callback is defined, because JS itself doesn't allow you to share raw JS values (including callbacks) between Workers and main thread. But "the same thread" doesn't necessarily have to mean "main thread", you might have callbacks defined in workers too.

But how would this work, since the callback is created only once during a users initialisation, example:
https://github.com/Birch-san/box2d3-wasm/blob/ac5f032db17bf94e5c4aaddcd177977536441a94/demo/samples/categories/events/platformer.mjs#L44

This call can't be proxied since the callback has a return value (as far as I am aware). Could you roughly sketch out how I could get this to work without tinkering with the Box2D source.

@RReverser
Copy link
Collaborator

This call can't be proxied since the callback has a return value (as far as I am aware).

Yeah those are rather annoying and I agree - #20611 - Emscripten proxying API should allow for returning values natively, but for now you can work around that by having a std::optional that you write into from the allowed void callback in one thread, and read from in another thread.

You can see an example of how I did that in this helper in libusb: https://github.com/libusb/libusb/blob/9cef804b2454a2226f5fa5db79a7e9aa8a45d4d4/libusb/os/emscripten_webusb.cpp#L197-L204

@sbc100 sbc100 added the embind label May 5, 2025
@ErikSom
Copy link
Author

ErikSom commented May 9, 2025

@RReverser thank you so much for sharing that piece of code :) its really clever! I considered implementing it, but in my case the PreSolve callback runs every step, which means all threads will stall until the main thread runs, basically making my simulation run on a single thread not giving any benefit over forcing users to use single threaded with PreSolve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants