Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions crates/wasmtime/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,25 @@ impl Store {
&self.inner.frame_info
}

/// Notifies that the current Store (and all referenced entities) has been moved over to a
/// different thread.
///
/// See also the multithreading documentation for more details:
/// <https://docs.wasmtime.dev/examples-rust-multithreading.html>.
///
/// # Safety
///
/// In general, it's not possible to move a `Store` to a different thread, because it isn't `Send`.
/// That being said, it is possible to create an unsafe `Send` wrapper over a `Store`, assuming
/// the safety guidelines exposed in the multithreading documentation have been applied. So it
/// is in general unnecessary to do this, if you're not doing unsafe things.
///
/// It is fine to call this several times: only the first call will have an effect.
pub unsafe fn notify_switched_thread(&self) {
wasmtime_runtime::init_traps(frame_info::GlobalFrameInfo::is_wasm_pc)
.expect("failed to initialize per-threads traps");
}

/// Perform garbage collection of `ExternRef`s.
pub fn gc(&self) {
// For this crate's API, we ensure that `set_stack_canary` invariants
Expand Down
15 changes: 10 additions & 5 deletions docs/examples-rust-multithreading.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,16 @@ some possibilities include:
`Store::set` or `Func::wrap`) implement the `Send` trait.

If these requirements are met it is technically safe to move a store and its
objects between threads. The reason that this strategy isn't recommended,
however, is that you will receive no assistance from the Rust compiler in
verifying that the transfer across threads is indeed actually safe. This will
require auditing your embedding of Wasmtime itself to ensure it meets these
requirements.
objects between threads. When you move a store to another thread, it is
required that you run the `Store::notify_switched_thread()` method after the
store has landed on the new thread, so that per-thread initialization is
correctly re-run. Failure to do so may cause wasm traps to crash the whole
application.

The reason that this strategy isn't recommended, however, is that you will
receive no assistance from the Rust compiler in verifying that the transfer
across threads is indeed actually safe. This will require auditing your
embedding of Wasmtime itself to ensure it meets these requirements.

It's important to note that the requirements here also apply to the futures
returned from `Func::call_async`. These futures are not `Send` due to them
Expand Down
39 changes: 39 additions & 0 deletions tests/all/traps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,42 @@ note: run with `WASMTIME_BACKTRACE_DETAILS=1` environment variable to display mo
);
Ok(())
}

#[test]
fn multithreaded_traps() -> Result<()> {
// Compile and run unreachable on a thread, then moves over the whole store to another thread,
// and make sure traps are still correctly caught after notifying the store of the move.
let instance = {
let store = Store::default();
let module = Module::new(
store.engine(),
r#"(module (func (export "run") unreachable))"#,
)?;
Instance::new(&store, &module, &[])?
};

assert!(instance.get_typed_func::<(), ()>("run")?.call(()).is_err());

struct SendInstance {
inner: Instance,
}
unsafe impl Send for SendInstance {}

let instance = SendInstance { inner: instance };

let handle = std::thread::spawn(move || {
let instance = instance.inner;
unsafe {
instance.store().notify_switched_thread();
}
assert!(instance
.get_typed_func::<(), ()>("run")
.unwrap()
.call(())
.is_err());
});

handle.join().expect("couldn't join thread");

Ok(())
}