1 #![cfg(not(miri))] 2 3 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; 4 use wasmtime::*; 5 6 fn interruptable_store() -> Store<()> { 7 let engine = Engine::new(Config::new().epoch_interruption(true)).unwrap(); 8 let mut store = Store::new(&engine, ()); 9 store.set_epoch_deadline(1); 10 store 11 } 12 13 fn hugely_recursive_module(engine: &Engine) -> anyhow::Result<Module> { 14 let mut wat = String::new(); 15 wat.push_str( 16 r#" 17 (import "" "" (func)) 18 (func (export "loop") call 2 call 2) 19 "#, 20 ); 21 for i in 0..100 { 22 wat.push_str(&format!("(func call {0} call {0})\n", i + 3)); 23 } 24 wat.push_str("(func call 0)\n"); 25 26 Module::new(engine, &wat) 27 } 28 29 #[test] 30 fn loops_interruptable() -> anyhow::Result<()> { 31 let mut store = interruptable_store(); 32 let module = Module::new(store.engine(), r#"(func (export "loop") (loop br 0))"#)?; 33 let instance = Instance::new(&mut store, &module, &[])?; 34 let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; 35 store.engine().increment_epoch(); 36 let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?; 37 assert_eq!(trap, Trap::Interrupt); 38 Ok(()) 39 } 40 41 #[test] 42 fn functions_interruptable() -> anyhow::Result<()> { 43 let mut store = interruptable_store(); 44 let module = hugely_recursive_module(store.engine())?; 45 let func = Func::wrap(&mut store, || {}); 46 let instance = Instance::new(&mut store, &module, &[func.into()])?; 47 let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; 48 store.engine().increment_epoch(); 49 let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?; 50 assert_eq!(trap, Trap::Interrupt); 51 Ok(()) 52 } 53 54 const NUM_HITS: usize = 100_000; 55 56 #[test] 57 fn loop_interrupt_from_afar() -> anyhow::Result<()> { 58 // Create an instance which calls an imported function on each iteration of 59 // the loop so we can count the number of loop iterations we've executed so 60 // far. 61 static HITS: AtomicUsize = AtomicUsize::new(0); 62 static STOP: AtomicBool = AtomicBool::new(false); 63 let mut store = interruptable_store(); 64 let module = Module::new( 65 store.engine(), 66 r#" 67 (import "" "" (func)) 68 69 (func (export "loop") 70 (loop 71 call 0 72 br 0) 73 ) 74 "#, 75 )?; 76 let func = Func::wrap(&mut store, || { 77 HITS.fetch_add(1, SeqCst); 78 }); 79 let instance = Instance::new(&mut store, &module, &[func.into()])?; 80 81 // Use the engine to wait for it to enter the loop long enough and then we 82 // signal an interrupt happens. 83 let engine = store.engine().clone(); 84 let thread = std::thread::spawn(move || { 85 while HITS.load(SeqCst) <= NUM_HITS && !STOP.load(SeqCst) { 86 // continue ... 87 } 88 println!("interrupting"); 89 engine.increment_epoch(); 90 }); 91 92 // Enter the infinitely looping function and assert that our interrupt 93 // handle does indeed actually interrupt the function. 94 let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; 95 let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?; 96 STOP.store(true, SeqCst); 97 thread.join().unwrap(); 98 assert!(HITS.load(SeqCst) > NUM_HITS); 99 assert_eq!(trap, Trap::Interrupt); 100 Ok(()) 101 } 102 103 #[test] 104 fn function_interrupt_from_afar() -> anyhow::Result<()> { 105 // Create an instance which calls an imported function on each iteration of 106 // the loop so we can count the number of loop iterations we've executed so 107 // far. 108 static HITS: AtomicUsize = AtomicUsize::new(0); 109 static STOP: AtomicBool = AtomicBool::new(false); 110 111 let mut store = interruptable_store(); 112 let module = hugely_recursive_module(store.engine())?; 113 let func = Func::wrap(&mut store, || { 114 HITS.fetch_add(1, SeqCst); 115 }); 116 let instance = Instance::new(&mut store, &module, &[func.into()])?; 117 118 // Use the instance's interrupt handle to wait for it to enter the loop long 119 // enough and then we signal an interrupt happens. 120 let engine = store.engine().clone(); 121 let thread = std::thread::spawn(move || { 122 while HITS.load(SeqCst) <= NUM_HITS && !STOP.load(SeqCst) { 123 // continue ... 124 } 125 engine.increment_epoch(); 126 }); 127 128 // Enter the infinitely looping function and assert that our interrupt 129 // handle does indeed actually interrupt the function. 130 let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?; 131 let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?; 132 STOP.store(true, SeqCst); 133 thread.join().unwrap(); 134 assert!(HITS.load(SeqCst) > NUM_HITS); 135 assert_eq!(trap, Trap::Interrupt); 136 Ok(()) 137 } 138