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