This crate provides an executor for asynchronous task with the same
API as futures_executor::ThreadPool
targeting the web browser
environment. Instead of using spawning threads via std::thread
, web
workers are created. This crate tries hard to make this process as
seamless and painless as possible.
use futures::channel::mpsc;
use futures::StreamExt;
use js_sys::Promise;
use wasm_bindgen::prelude::*;
use wasm_futures_executor::ThreadPool;
#[wasm_bindgen]
pub async fn start() -> Result<JsValue, JsValue> {
let pool = ThreadPool::max_threads().await?;
let (tx, mut rx) = mpsc::channel(10);
for i in 0..20 {
let mut tx_c = tx.clone();
pool.spawn_ok(async move {
tx_c.start_send(i * i).unwrap();
});
}
drop(tx);
let mut i = 0;
while let Some(x) = rx.next().await {
i += x;
}
Ok(i.into())
}
.. and using it:
import init, { start } from './sample.js';
async function run() {
await init();
const res = await start();
console.log("result", res);
}
run();
And build your project with:
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \
cargo +nightly build --target wasm32-unknown-unknown --release -Z build-std=std,panic_abort
wasm-bindgen \
./target/wasm32-unknown-unknown/release/sample.wasm \
--out-dir . \
--target web \
--weak-refs
.. or if you want to use wasm-pack build -t web
, make sure to set up
the nightly toolchain and the proper RUSTFLAGS
(for example by
providing creating the rust-toolchain.toml
and .cargo/config
files
like done in this repo).
Please have a look at the sample for a complete end-to-end example project without bundlers, and sample-webpack using Webpack 5.
Note: This crate requires usage of the web
target of
wasm-bindgen
/wasm-pack
. Given the broad standardization on ES
Modules, this should be mostly fine.
Similar to the async executor provided by
futures-executor
,
multiple worker "threads" are spawned upon instantation of the
ThreadPool
. Each thread is a web worker, which loads some js glue
code. The glue code is provided as a js snippet, which is linked from
the wasm-bindgen generated js glue code. This way, it's transparent to
any bundlers.
Each web worker is constructed with the following arguments:
- WebAssembly module initialization and its shared memory
(
SharedArrayBuffer
). - The third one is a pointer to some shared state, including a channel,
where the async tasks are passed in. The library provides the
worker_entry_point
function for this purpose.
Once the ThreadPool
is dropped, all channels are closed and the web
workers are terminated.
Unfortunately, this requires a nightly compilers because Rust's standard library needs to be recompiled with the following unstable features:
atomics
: (rust feature) supports for wasm atomics, see rust-lang/rust#77839bulk-memory
: (llvm feature) generation of atomic instruction, shared memory, passive segments, etc.mutable-globals
: (llvm feature)
A note: When workers are destroyed, some memory might be leaked (for
example thread local storage or the thead's stack). I recommend
passing the --weak-ref
option to wasm-bindgen
in order to let
wasm-bindgen create user defined finalizers according to the WeakRef
proposal.
In general, implementing wasm threads in Rust seems to have lost some of its traction ..
Sharing memory via
SharedArrayBuffer
will soon (starting with Chrome 92) require setting the right headers
to mark the website as "cross-origin isolated", meaning that the
following headers need to be sent with the main document:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
For more information check out this link.
Loading modules in web workers is currently only supported in Chromium. There seems the be finally some progress in Firefox. As a workaround, you can include a workers-polyfill.
If you're targeting older browser, you might want to do some feature detection and fall back gracefully -- but that's out of scope for this documentation.
There is a significant overhead of sending and spawning futures across the thread boundary. It makes most sense for long-lived tasks (check out the factorial demo, which is a rough 3x performance increase). As always, make sure to profile your use case.
-
The first and foremost resource is the great raytracing demo of the wasm-bindgen docs and its background blog post.
-
The wasm-bindgen-rayon crate provides extensive documentation and a nice end-to-end example.
-
The wasm_thread crate is quite similar in its approach as this crate. The main difference is the usage of ES modules.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.