Interrupting Wasm Execution
If you want to interrupt Wasm execution, for example to prevent an infinite loop in the Wasm guest from indefinitely blocking the host, Wasmtime provides two mechanisms you can choose between. Wasmtime also allows you to choose what happens when Wasm execution is interrupted.
What Happens When Execution is Interrupted
When a Wasm program's execution is interrupted, you can configure Wasmtime to do either of the following:
-
Raise a trap: This terminates the current Wasm program, and it is not resumable.
-
Async yield: This pauses the current Wasm program, yields control back to the host, and lets the host decide whether to resume execution sometime in the future.
These options are both available regardless of which interruption mechanism you employ.
Interruption Mechanisms
Deterministic Fuel
Fuel-based interruption is completely deterministic: the same program run with the same amount of fuel will always be interrupted at the same location in the program (unless it has enough fuel to complete its computation, or there is some other form of non-determinism that causes the program to behave differently).
The downside is that fuel-based interruption imposes more overhead on execution, slowing down Wasm programs, than epochs do.
//! Example of limiting a WebAssembly function's runtime using "fuel consumption".
// You can execute this example with `cargo run --example fuel`
use wasmtime::*;
fn main() -> Result<()> {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
store.set_fuel(10_000)?;
let module = Module::from_file(store.engine(), "examples/fuel.wat")?;
let instance = Instance::new(&mut store, &module, &[])?;
// Invoke `fibonacci` export with higher and higher numbers until we exhaust our fuel.
let fibonacci = instance.get_typed_func::<i32, i32>(&mut store, "fibonacci")?;
for n in 1.. {
let fuel_before = store.get_fuel().unwrap();
let output = match fibonacci.call(&mut store, n) {
Ok(v) => v,
Err(e) => {
assert_eq!(e.downcast::<Trap>()?, Trap::OutOfFuel);
println!("Exhausted fuel computing fib({n})");
break;
}
};
let fuel_consumed = fuel_before - store.get_fuel().unwrap();
println!("fib({n}) = {output} [consumed {fuel_consumed} fuel]");
store.set_fuel(10_000)?;
}
Ok(())
}
See these API docs for more details:
wasmtime::Config::consume_fuel
wasmtime::Config::set_fuel
wasmtime::Config::fuel_async_yield_interval
Non-Deterministic Epochs
Epoch-based interruption imposes relatively low overhead on Wasm execution; it has been measured at around a 10% slowdown. It is faster than fuel-based interruption.
The downside is that it is non-deterministic. Running the same program with the same inputs for one epoch might result in an interrupt at one location the first time, a later location the second time, or even complete successfully another time. This is because it is based on wall-time rather than an exact count of how many Wasm instructions are executed.
//! 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(())
}
See these API docs for more details: