diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 18dbde303c..4c78e82c8f 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -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: + /// . + /// + /// # 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 diff --git a/docs/examples-rust-multithreading.md b/docs/examples-rust-multithreading.md index 5d3fdcac2c..d99977759f 100644 --- a/docs/examples-rust-multithreading.md +++ b/docs/examples-rust-multithreading.md @@ -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 diff --git a/tests/all/traps.rs b/tests/all/traps.rs index 4fcc6f3882..e409b98189 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -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(()) +}