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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user