-
Notifications
You must be signed in to change notification settings - Fork 506
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
Use wasm-sync to allow usage on the main browser thread #1110
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the current failure mode when it tries to block on the main web thread? Some kind of panic/abort of the wasm module?
Yeah. More specifically, Wasm engine itself throws an error on the instruction, so it's not even something one can catch & handle from Wasm. |
One of the most common complaints I've been receiving in [wasm-bindgen-rayon](https://github.com/RReverser/wasm-bindgen-rayon) that prevents people from using Rayon on the Web is the complexity of manually splitting up the code that uses Rayon into a Web Worker from code that drives the UI. It requires custom message passing for proxying between two threads (Workers), which, admittedly, feels particularly silly when using a tool that is meant to simplify working with threads for you. This all stems from a [Wasm limitation](WebAssembly/threads#177) that disallows `atomic.wait` on the main browser thread. In theory, it's a reasonable limitation, since blocking main thread on the web is more problematic than on other platforms as it blocks web app's UI from being responsive altogether, and because there is no limit on how long atomic wait can block. In practice, however, it causes enough issues for users that various toolchains - even Emscripten - work around this issue by spin-locking when on the main thread. Rust / wasm-bindgen decided not to adopt the same workaround, following general Wasm limitation, which is also a fair stance for general implementation of `Mutex` and other blocking primitives, but I believe Rayon usecase is quite different. Code using parallel iterators is almost always guaranteed to run for less or, worst-case, ~same time as code using regular iterators, so it doesn't make sense to "punish" Rayon users and prevent them from being able to use parallel iterators on the main thread when it will lead to a _more_ responsive UI than using regular iterators. This PR adds a `cfg`-conditional dependency on [wasm_sync](https://docs.rs/wasm_sync/latest/wasm_sync/) that automatically switches to allowed spin-based `Mutex` and `Condvar` when it detects it's running on the main thread, and to regular `std::sync` based implementation otherwise, thus avoiding the `atomics.wait` error. This dependency will only be added when building for `wasm32-unknown-unknown` - that is, not affecting WASI and Emscripten users - and only when building with `-C target-feature=+atomics`, so not affecting users who rely on Rayon's single-threaded fallback mode either. I hope this kind of very limited override will be acceptable as it makes it much easier to use Rayon on the web. When this is merged, I'll be able to leverage it in wasm-bindgen-rayon and [significantly simplify](https://github.com/RReverser/wasm-bindgen-rayon/compare/main...RReverser:wasm-bindgen-rayon:wasm-sync?expand=1) demos, tests and docs by avoiding that extra Worker machinery (e.g. see `demo/wasm-worker.js` and `demo/index.js` merged into single simple JS file in the linked diff).
Would it be possible to test this in CI? |
I am testing this on CI on the wasm-bindgen-rayon side, but I was also wondering if we should move / add CI covering usage with wasm-bindgen-rayon for rayon too, given that you have a more comprehensive test suite... Running tests requires some plumbing - as in, regular |
If runtime in CI is complicated, then maybe just a build test is enough. I suppose that needs |
Just for a build test - I think not necessarily, enabling target-feature for Rayon itself (via |
I'll try to add such basic check. |
Heh made a mistake of trying to add Will stick to the original scope instead. |
@cuviper Done. I split out WASI CI from Wasm CI since they do different things, and because I need Btw, I'm curious - why are Wasm sections set to only run on |
The idea was to keep the PR CI fast like rust-lang/rust does, since most changes are not target specific, and the macos/windows builders are noticeably slower. The merge queue is supposed to make sure that we do get a full run so we'll notice before any target-specific break actually reaches the repo. But these days, maybe those slower builders are still fast enough that we don't need to bother splitting... |
Co-authored-by: Josh Stone <cuviper@gmail.com>
Hm one more thought: I wonder if it would be better to require an explicit Cargo feature for this, rather than assuming that all It would be a bit more verbose, but perhaps explicit opt-in is better. |
OTOH that doesn't really enable JS support like in other crates - you still need wasm-bindgen-rayon as well. I guess it should be called |
Maybe just leave it directly as |
Not sure about this one yet, but:
|
I added an explicit |
In fact, I already wonder if I should use spin instead, as it's more generic (any I can investigate and switch in a follow-up PR though, for now this PR is already a big improvement over previous state of art and would rather not block it on further investigations. |
It's significant that |
Co-authored-by: Josh Stone <cuviper@gmail.com>
Yeah that's true. I raised an issue on |
FWIW this PR should be good to go. |
Thanks! |
I guess macOS CI just hung up? |
Hmm, let's try again. |
Starting with rayon 1.8.1 / rayon-core 1.12.1, Rayon has a web_spin_lock feature powered by wasm-sync that allows blocking on the main thread via spinning - same workaround for forbidden `atomics.wait` as used in e.g. Emscripten. rayon-rs/rayon#1110 We can leverage it and simplify instructions, tests and the demo to avoid an extra worker.
One of the most common complaints I've been receiving in wasm-bindgen-rayon that prevents people from using Rayon on the Web is the complexity of manually splitting up the code that uses Rayon into a Web Worker from code that drives the UI.
It requires custom message passing for proxying between two threads (Workers), which, admittedly, feels particularly silly when using a tool that is meant to simplify working with threads for you.
This all stems from a Wasm limitation that disallows
atomic.wait
on the main browser thread. In theory, it's a reasonable limitation, since blocking main thread on the web is more problematic than on other platforms as it blocks web app's UI from being responsive altogether, and because there is no limit on how long atomic wait can block. In practice, however, it causes enough issues for users that various toolchains - even Emscripten - work around this issue by spin-locking when on the main thread.Rust / wasm-bindgen decided not to adopt the same workaround, following the core Wasm decision, which is also a fair stance for general implementation of
Mutex
and other blocking primitives, but I believe Rayon usecase is sufficiently special. Code using parallel iterators is almost always guaranteed to run for less or, worst-case, ~same time as code using regular iterators, so it doesn't make sense to "punish" Rayon users and prevent them from being able to use parallel iterators on the main thread when it will lead to a more responsive UI than using regular iterators.This PR adds a
cfg
-conditional dependency on wasm_sync that automatically switches to the allowed spin-basedMutex
andCondvar
when it detects it's running on the main thread, and to regularstd::sync
based implementation otherwise, thus avoiding theatomics.wait
error. This dependency will only be added when building forwasm32-unknown-unknown
- that is, not affecting WASI and Emscripten users - and only when building with-C target-feature=+atomics
, so not affecting users who rely on Rayon's single-threaded fallback mode either. I hope this kind of very limited override will be acceptable as it makes it much easier to use Rayon on the web.When this is merged, I'll be able to leverage it in wasm-bindgen-rayon and significantly simplify demos, tests and docs by avoiding that extra Worker machinery (e.g. see
demo/wasm-worker.js
anddemo/index.js
merged into single simple JS file in the linked diff).