Skip to content

Commit 1f4f16e

Browse files
committed
Allow using Rayon on the main thread
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.
1 parent 4ee8e54 commit 1f4f16e

File tree

11 files changed

+85
-125
lines changed

11 files changed

+85
-125
lines changed

Cargo.lock

Lines changed: 34 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ readme = "README.md"
1111
exclude = [".github"]
1212
repository = "https://github.com/RReverser/wasm-bindgen-rayon"
1313

14-
[dependencies]
14+
[workspace.dependencies]
1515
wasm-bindgen = "0.2.84"
16-
rayon-core = "1.12"
17-
spmc = "0.3.0"
16+
rayon = "1.8.1"
17+
18+
[dependencies]
19+
wasm-bindgen = { workspace = true }
20+
rayon-core = { version = "1.12.1", features = ["web_spin_lock"] }
21+
crossbeam-channel = "0.5.9"
1822
js-sys = "0.3.48"
1923

2024
[workspace]

README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,9 @@ For a quick demo, check out <https://rreverser.com/wasm-bindgen-rayon-demo/>.
3131

3232
Before we get started, check out caveats listed in the [wasm-bindgen threading docs](https://rustwasm.github.io/wasm-bindgen/examples/raytrace.html). While this library specifically targets Rayon and automatically provides the necessary shims for you, some of the caveats still apply.
3333

34-
Most notably, even when you're using multithreading, the main thread still **can't be blocked** while waiting for the Rayon pool to get ready or for all the background operations to finish.
35-
36-
You must instantiate the main JS+Wasm in a dedicated `Worker` to avoid blocking the main thread - that is, don't mix UI and Rayon code together. Instead, use a library like [Comlink](https://github.com/GoogleChromeLabs/comlink) or a custom glue code to expose required wasm-bindgen methods to the main thread, and do the UI work from there.
37-
3834
## Setting up
3935

40-
First of all, in order to use `SharedArrayBuffer` on the Web, you need to enable [cross-origin isolation policies](https://web.dev/coop-coep/). Check out the linked article for details.
36+
In order to use `SharedArrayBuffer` on the Web, you need to enable [cross-origin isolation policies](https://web.dev/coop-coep/). Check out the linked article for details.
4137

4238
Then, add this crate as a dependency to your `Cargo.toml` (in addition to `wasm-bindgen` and `rayon` themselves):
4339

@@ -194,8 +190,6 @@ If you want to build this library for usage without bundlers, enable the `no-bun
194190
wasm-bindgen-rayon = { version = "1.0", features = ["no-bundler"] }
195191
```
196192

197-
Note that, in addition to the earlier mentioned restrictions, this will work out of the box only in browsers with [support for Module Workers](https://caniuse.com/mdn-api_worker_worker_ecmascript_modules). To ensure that it works in all browsers, either bundle your code for production, or include [module-workers-polyfill](https://unpkg.com/module-workers-polyfill) on the same page.
198-
199193
# License
200194

201195
This crate is licensed under the Apache-2.0 license.

demo/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ crate-type = ["cdylib"]
1010

1111
[dependencies]
1212
wasm-bindgen-rayon = { path = "..", optional = true }
13-
wasm-bindgen = "0.2.74"
14-
rayon = { version = "1.8", optional = true }
13+
wasm-bindgen = { workspace = true }
14+
rayon = { workspace = true, optional = true }
1515
num-complex = "0.4.0"
1616
once_cell = "1.7.2"
1717
getrandom = { version = "0.2.2", features = ["js"] }

demo/index.js

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,27 @@
1111
* limitations under the License.
1212
*/
1313

14-
import * as Comlink from 'comlink';
14+
import { threads } from 'wasm-feature-detect';
1515

1616
const maxIterations = 1000;
1717

18-
const canvas = document.getElementById('canvas');
18+
const canvas = /** @type {HTMLCanvasElement} */ (
19+
document.getElementById('canvas')
20+
);
1921
const { width, height } = canvas;
2022
const ctx = canvas.getContext('2d');
21-
const timeOutput = document.getElementById('time');
23+
const timeOutput = /** @type {HTMLOutputElement} */ (
24+
document.getElementById('time')
25+
);
2226

23-
// Create a separate thread from wasm-worker.js and get a proxy to its handlers.
24-
let handlers = await Comlink.wrap(
25-
new Worker(new URL('./wasm-worker.js', import.meta.url), {
26-
type: 'module'
27-
})
28-
).handlers;
29-
30-
function setupBtn(id) {
31-
// Handlers are named in the same way as buttons.
32-
let handler = handlers[id];
33-
// If handler doesn't exist, it's not supported.
34-
if (!handler) return;
27+
function setupBtn(id, { generate }) {
3528
// Assign onclick handler + enable the button.
3629
Object.assign(document.getElementById(id), {
3730
async onclick() {
38-
let { rawImageData, time } = await handler({
39-
width,
40-
height,
41-
maxIterations
42-
});
31+
const start = performance.now();
32+
const rawImageData = generate(width, height, maxIterations);
33+
const time = performance.now() - start;
34+
4335
timeOutput.value = `${time.toFixed(2)} ms`;
4436
const imgData = new ImageData(rawImageData, width, height);
4537
ctx.putImageData(imgData, 0, 0);
@@ -48,7 +40,16 @@ function setupBtn(id) {
4840
});
4941
}
5042

51-
setupBtn('singleThread');
52-
if (await handlers.supportsThreads) {
53-
setupBtn('multiThread');
54-
}
43+
(async function initSingleThread() {
44+
const singleThread = await import('./pkg/wasm_bindgen_rayon_demo.js');
45+
await singleThread.default();
46+
setupBtn('singleThread', singleThread);
47+
})();
48+
49+
(async function initMultiThread() {
50+
if (!(await threads())) return;
51+
const multiThread = await import('./pkg-parallel/wasm_bindgen_rayon_demo.js');
52+
await multiThread.default();
53+
await multiThread.initThreadPool(navigator.hardwareConcurrency);
54+
setupBtn('multiThread', multiThread);
55+
})();

demo/wasm-worker.js

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
#[cfg(all(not(doc), not(target_feature = "atomics")))]
2020
compile_error!("Did you forget to enable `atomics` and `bulk-memory` features as outlined in wasm-bindgen-rayon README?");
2121

22+
use crossbeam_channel::{bounded, Receiver, Sender};
2223
use js_sys::Promise;
23-
use spmc::{channel, Receiver, Sender};
2424
use wasm_bindgen::prelude::*;
2525
use wasm_bindgen::JsValue;
2626

@@ -60,7 +60,7 @@ fn _ensure_worker_emitted() {
6060
#[wasm_bindgen]
6161
impl wbg_rayon_PoolBuilder {
6262
fn new(num_threads: usize) -> Self {
63-
let (sender, receiver) = channel();
63+
let (sender, receiver) = bounded(num_threads);
6464
Self {
6565
num_threads,
6666
sender,

test/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ crate-type = ["cdylib"]
1010

1111
[dependencies]
1212
wasm-bindgen-rayon = { path = ".." }
13-
wasm-bindgen = "0.2.74"
14-
rayon = "1.8"
13+
wasm-bindgen = { workspace = true }
14+
rayon = { workspace = true }

test/index.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,18 @@
1111
* limitations under the License.
1212
*/
1313

14+
import init, { initThreadPool, sum } from './pkg/test.js';
15+
16+
await init();
17+
await initThreadPool(navigator.hardwareConcurrency);
18+
// 1...10
19+
let arr = Int32Array.from({ length: 10 }, (_, i) => i + 1);
20+
if (sum(arr) !== 55) {
21+
throw new Error('Wrong result.');
22+
}
23+
1424
// Note: this will be overridden by the Playwright test runner.
1525
// The default implementation is provided only for manual testing.
1626
globalThis.onDone ??= () => console.log('OK');
1727

18-
new Worker(new URL('./index.worker.js', import.meta.url), {
19-
type: 'module'
20-
}).addEventListener('message', globalThis.onDone);
28+
onDone();

test/index.worker.js

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)