#![cfg(not(miri))] use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; use wasmtime::*; fn interruptable_store() -> Store<()> { let engine = Engine::new(Config::new().epoch_interruption(true)).unwrap(); let mut store = Store::new(&engine, ()); store.set_epoch_deadline(1); store } fn hugely_recursive_module(engine: &Engine) -> anyhow::Result { let mut wat = String::new(); wat.push_str( r#" (import "" "" (func)) (func (export "loop") call 2 call 2) "#, ); for i in 0..100 { wat.push_str(&format!("(func call {0} call {0})\n", i + 3)); } wat.push_str("(func call 0)\n"); Module::new(engine, &wat) } #[test] fn loops_interruptable() -> anyhow::Result<()> { let mut store = interruptable_store(); let module = Module::new(store.engine(), r#"(func (export "loop") (loop br 0))"#)?; let instance = Instance::new(&mut store, &module, &[])?; let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; store.engine().increment_epoch(); let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; assert_eq!(trap, Trap::Interrupt); Ok(()) } #[test] fn functions_interruptable() -> anyhow::Result<()> { let mut store = interruptable_store(); let module = hugely_recursive_module(store.engine())?; let func = Func::wrap(&mut store, || {}); let instance = Instance::new(&mut store, &module, &[func.into()])?; let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; store.engine().increment_epoch(); let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; assert_eq!(trap, Trap::Interrupt); Ok(()) } const NUM_HITS: usize = 100_000; #[test] fn loop_interrupt_from_afar() -> anyhow::Result<()> { // Create an instance which calls an imported function on each iteration of // the loop so we can count the number of loop iterations we've executed so // far. static HITS: AtomicUsize = AtomicUsize::new(0); static STOP: AtomicBool = AtomicBool::new(false); let mut store = interruptable_store(); let module = Module::new( store.engine(), r#" (import "" "" (func)) (func (export "loop") (loop call 0 br 0) ) "#, )?; let func = Func::wrap(&mut store, || { HITS.fetch_add(1, SeqCst); }); let instance = Instance::new(&mut store, &module, &[func.into()])?; // Use the engine to wait for it to enter the loop long enough and then we // signal an interrupt happens. let engine = store.engine().clone(); let thread = std::thread::spawn(move || { while HITS.load(SeqCst) <= NUM_HITS && !STOP.load(SeqCst) { // continue ... } println!("interrupting"); engine.increment_epoch(); }); // Enter the infinitely looping function and assert that our interrupt // handle does indeed actually interrupt the function. let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; STOP.store(true, SeqCst); thread.join().unwrap(); assert!(HITS.load(SeqCst) > NUM_HITS); assert_eq!(trap, Trap::Interrupt); Ok(()) } #[test] fn function_interrupt_from_afar() -> anyhow::Result<()> { // Create an instance which calls an imported function on each iteration of // the loop so we can count the number of loop iterations we've executed so // far. static HITS: AtomicUsize = AtomicUsize::new(0); static STOP: AtomicBool = AtomicBool::new(false); let mut store = interruptable_store(); let module = hugely_recursive_module(store.engine())?; let func = Func::wrap(&mut store, || { HITS.fetch_add(1, SeqCst); }); let instance = Instance::new(&mut store, &module, &[func.into()])?; // Use the instance's interrupt handle to wait for it to enter the loop long // enough and then we signal an interrupt happens. let engine = store.engine().clone(); let thread = std::thread::spawn(move || { while HITS.load(SeqCst) <= NUM_HITS && !STOP.load(SeqCst) { // continue ... } engine.increment_epoch(); }); // Enter the infinitely looping function and assert that our interrupt // handle does indeed actually interrupt the function. let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; STOP.store(true, SeqCst); thread.join().unwrap(); assert!(HITS.load(SeqCst) > NUM_HITS); assert_eq!(trap, Trap::Interrupt); Ok(()) }