Add epoch-based interruption for cooperative async timeslicing.
This PR introduces a new way of performing cooperative timeslicing that is intended to replace the "fuel" mechanism. The tradeoff is that this mechanism interrupts with less precision: not at deterministic points where fuel runs out, but rather when the Engine enters a new epoch. The generated code instrumentation is substantially faster, however, because it does not need to do as much work as when tracking fuel; it only loads the global "epoch counter" and does a compare-and-branch at backedges and function prologues. This change has been measured as ~twice as fast as fuel-based timeslicing for some workloads, especially control-flow-intensive workloads such as the SpiderMonkey JS interpreter on Wasm/WASI. The intended interface is that the embedder of the `Engine` performs an `engine.increment_epoch()` call periodically, e.g. once per millisecond. An async invocation of a Wasm guest on a `Store` can specify a number of epoch-ticks that are allowed before an async yield back to the executor's event loop. (The initial amount and automatic "refills" are configured on the `Store`, just as for fuel.) This call does only signal-safe work (it increments an `AtomicU64`) so could be invoked from a periodic signal, or from a thread that wakes up once per period.
This commit is contained in:
48
examples/epochs.rs
Normal file
48
examples/epochs.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
//! Example of interrupting a WebAssembly function's runtime via epoch
|
||||
//! changes ("epoch interruption") in a synchronous context. To see
|
||||
//! an example of setup for asynchronous usage, see
|
||||
//! `tests/all/epoch_interruption.rs`
|
||||
|
||||
use anyhow::Error;
|
||||
use std::sync::Arc;
|
||||
use wasmtime::{Config, Engine, Instance, Module, Store};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
// Set up an engine configured with epoch interruption enabled.
|
||||
let mut config = Config::new();
|
||||
config.epoch_interruption(true);
|
||||
let engine = Arc::new(Engine::new(&config)?);
|
||||
|
||||
let mut store = Store::new(&engine, ());
|
||||
// Configure the store to trap on reaching the epoch deadline.
|
||||
// This is the default, but we do it explicitly here to
|
||||
// demonstrate.
|
||||
store.epoch_deadline_trap();
|
||||
// Configure the store to have an initial epoch deadline one tick
|
||||
// in the future.
|
||||
store.set_epoch_deadline(1);
|
||||
|
||||
// Reuse the fibonacci function from the Fuel example. This is a
|
||||
// long-running function that we will want to interrupt.
|
||||
let module = Module::from_file(store.engine(), "examples/fuel.wat")?;
|
||||
let instance = Instance::new(&mut store, &module, &[])?;
|
||||
|
||||
// Start a thread that will bump the epoch after 1 second.
|
||||
let engine_clone = engine.clone();
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
engine_clone.increment_epoch();
|
||||
});
|
||||
|
||||
// Invoke `fibonacci` with a large argument such that a normal
|
||||
// invocation would take many seconds to complete.
|
||||
let fibonacci = instance.get_typed_func::<i32, i32, _>(&mut store, "fibonacci")?;
|
||||
match fibonacci.call(&mut store, 100) {
|
||||
Ok(_) => panic!("Somehow we computed recursive fib(100) in less than a second!"),
|
||||
Err(_) => {
|
||||
println!("Trapped out of fib(100) after epoch increment");
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user