Introduce a new API that allows notifying that a Store has moved to a new thread (#2822)

* Introduce a new API that allows notifying that a Store has moved to a new thread

* Add backlink to documentation, and mention the new API in the multithreading doc;
This commit is contained in:
Benjamin Bouvier
2021-04-16 18:15:35 +02:00
committed by GitHub
parent 82f6556bc2
commit ba73b458b8
3 changed files with 68 additions and 5 deletions

View File

@@ -498,6 +498,25 @@ impl Store {
&self.inner.frame_info &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. /// Perform garbage collection of `ExternRef`s.
pub fn gc(&self) { pub fn gc(&self) {
// For this crate's API, we ensure that `set_stack_canary` invariants // For this crate's API, we ensure that `set_stack_canary` invariants

View File

@@ -129,11 +129,16 @@ some possibilities include:
`Store::set` or `Func::wrap`) implement the `Send` trait. `Store::set` or `Func::wrap`) implement the `Send` trait.
If these requirements are met it is technically safe to move a store and its 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, objects between threads. When you move a store to another thread, it is
however, is that you will receive no assistance from the Rust compiler in required that you run the `Store::notify_switched_thread()` method after the
verifying that the transfer across threads is indeed actually safe. This will store has landed on the new thread, so that per-thread initialization is
require auditing your embedding of Wasmtime itself to ensure it meets these correctly re-run. Failure to do so may cause wasm traps to crash the whole
requirements. 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 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 returned from `Func::call_async`. These futures are not `Send` due to them

View File

@@ -591,3 +591,42 @@ note: run with `WASMTIME_BACKTRACE_DETAILS=1` environment variable to display mo
); );
Ok(()) 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(())
}