Path: blob/main/tests/all/epoch_interruption.rs
3068 views
#![cfg(not(miri))]12use crate::async_functions::{CountPending, PollOnce};3use std::sync::Arc;4use std::sync::atomic::{AtomicBool, Ordering};5use wasmtime::format_err;6use wasmtime::*;7use wasmtime_test_macros::wasmtime_test;89fn build_engine(config: &mut Config) -> Result<Arc<Engine>> {10config.epoch_interruption(true);11Ok(Arc::new(Engine::new(&config)?))12}1314fn make_env<T: 'static>(engine: &Engine) -> Linker<T> {15let mut linker = Linker::new(engine);16let engine = engine.clone();1718linker19.func_new(20"",21"bump_epoch",22FuncType::new(&engine, None, None),23move |_caller, _params, _results| {24engine.increment_epoch();25Ok(())26},27)28.unwrap();2930linker31}3233enum InterruptMode {34Trap,35Callback(fn(StoreContextMut<usize>) -> Result<UpdateDeadline>),36Yield(u64),37}3839/// Run a test with the given wasm, giving an initial deadline of40/// `initial` ticks in the future, and either configuring the wasm to41/// yield and set a deadline `delta` ticks in the future if `delta` is42/// `Some(..)` or trapping if `delta` is `None`.43///44/// Returns `Some((yields, store))` if function completed normally, giving45/// the number of yields that occurred, or `None` if a trap occurred.46async fn run_and_count_yields_or_trap<F: Fn(Arc<Engine>)>(47config: &mut Config,48wasm: &str,49initial: u64,50delta: InterruptMode,51setup_func: F,52) -> Result<Option<(usize, usize)>> {53let engine = build_engine(config)?;54let linker = make_env::<usize>(&engine);55let module = Module::new(&engine, wasm)?;56let mut store = Store::new(&engine, 0);57store.set_epoch_deadline(initial);58match delta {59InterruptMode::Yield(delta) => {60store.epoch_deadline_async_yield_and_update(delta);61}62InterruptMode::Callback(func) => {63store.epoch_deadline_callback(func);64}65InterruptMode::Trap => {66store.epoch_deadline_trap();67}68}6970let engine_clone = engine.clone();71setup_func(engine_clone);7273let instance = linker.instantiate_async(&mut store, &module).await?;74let f = instance.get_func(&mut store, "run").unwrap();75let (result, yields) =76CountPending::new(Box::pin(f.call_async(&mut store, &[], &mut []))).await;77let store = store.data();78Ok(result.ok().map(|_| (yields, *store)))79}8081#[wasmtime_test]82async fn epoch_yield_at_func_entry(config: &mut Config) -> Result<()> {83// Should yield at start of call to func $subfunc.84assert_eq!(85Some((1, 0)),86run_and_count_yields_or_trap(87config,88"89(module90(import \"\" \"bump_epoch\" (func $bump))91(func (export \"run\")92call $bump ;; bump epoch93call $subfunc) ;; call func; will notice new epoch and yield94(func $subfunc))95",961,97InterruptMode::Yield(1),98|_| {},99)100.await?101);102Ok(())103}104105#[wasmtime_test]106async fn epoch_yield_at_loop_header(config: &mut Config) -> Result<()> {107// Should yield at top of loop, once per five iters.108assert_eq!(109Some((2, 0)),110run_and_count_yields_or_trap(111config,112"113(module114(import \"\" \"bump_epoch\" (func $bump))115(func (export \"run\")116(local $i i32)117(local.set $i (i32.const 10))118(loop $l119call $bump120(br_if $l (local.tee $i (i32.sub (local.get $i) (i32.const 1)))))))121",1220,123InterruptMode::Yield(5),124|_| {},125)126.await?127);128Ok(())129}130131#[wasmtime_test]132async fn epoch_yield_immediate(config: &mut Config) -> Result<()> {133// We should see one yield immediately when the initial deadline134// is zero.135assert_eq!(136Some((1, 0)),137run_and_count_yields_or_trap(138config,139"140(module141(import \"\" \"bump_epoch\" (func $bump))142(func (export \"run\")))143",1440,145InterruptMode::Yield(1),146|_| {},147)148.await?149);150Ok(())151}152153#[wasmtime_test]154async fn epoch_yield_only_once(config: &mut Config) -> Result<()> {155// We should yield from the subfunction, and then when we return156// to the outer function and hit another loop header, we should157// not yield again (the double-check block will reload the correct158// epoch).159assert_eq!(160Some((1, 0)),161run_and_count_yields_or_trap(162config,163"164(module165(import \"\" \"bump_epoch\" (func $bump))166(func (export \"run\")167(local $i i32)168(call $subfunc)169(local.set $i (i32.const 0))170(loop $l171(br_if $l (i32.eq (i32.const 10)172(local.tee $i (i32.add (i32.const 1) (local.get $i)))))))173(func $subfunc174(call $bump)))175",1761,177InterruptMode::Yield(1),178|_| {},179)180.await?181);182Ok(())183}184185#[wasmtime_test]186async fn epoch_interrupt_infinite_loop(config: &mut Config) -> Result<()> {187assert_eq!(188None,189run_and_count_yields_or_trap(190config,191"192(module193(import \"\" \"bump_epoch\" (func $bump))194(func (export \"run\")195(loop $l196(br $l))))197",1981,199InterruptMode::Trap,200|engine| {201std::thread::spawn(move || {202std::thread::sleep(std::time::Duration::from_millis(50));203engine.increment_epoch();204});205},206)207.await?208);209Ok(())210}211212#[wasmtime_test]213async fn epoch_interrupt_function_entries(config: &mut Config) -> Result<()> {214assert_eq!(215None,216run_and_count_yields_or_trap(217config,218"219(module220(import \"\" \"bump_epoch\" (func $bump))221(func (export \"run\")222call $f1223call $f1224call $f1225call $f1226call $f1227call $f1228call $f1229call $f1230call $f1231call $f1)232(func $f1233call $f2234call $f2235call $f2236call $f2237call $f2238call $f2239call $f2240call $f2241call $f2242call $f2)243(func $f2244call $f3245call $f3246call $f3247call $f3248call $f3249call $f3250call $f3251call $f3252call $f3253call $f3)254(func $f3255call $f4256call $f4257call $f4258call $f4259call $f4260call $f4261call $f4262call $f4263call $f4264call $f4)265(func $f4266call $f5267call $f5268call $f5269call $f5270call $f5271call $f5272call $f5273call $f5274call $f5275call $f5)276(func $f5277call $f6278call $f6279call $f6280call $f6281call $f6282call $f6283call $f6284call $f6285call $f6286call $f6)287(func $f6288call $f7289call $f7290call $f7291call $f7292call $f7293call $f7294call $f7295call $f7296call $f7297call $f7)298(func $f7299call $f8300call $f8301call $f8302call $f8303call $f8304call $f8305call $f8306call $f8307call $f8308call $f8)309(func $f8310call $f9311call $f9312call $f9313call $f9314call $f9315call $f9316call $f9317call $f9318call $f9319call $f9)320(func $f9))321",3221,323InterruptMode::Trap,324|engine| {325std::thread::spawn(move || {326std::thread::sleep(std::time::Duration::from_millis(50));327engine.increment_epoch();328});329},330)331.await?332);333Ok(())334}335336#[wasmtime_test]337async fn epoch_callback_continue(config: &mut Config) -> Result<()> {338assert_eq!(339Some((0, 1)),340run_and_count_yields_or_trap(341config,342"343(module344(import \"\" \"bump_epoch\" (func $bump))345(func (export \"run\")346call $bump ;; bump epoch347call $subfunc) ;; call func; will notice new epoch and yield348(func $subfunc))349",3501,351InterruptMode::Callback(|mut cx| {352let s = cx.data_mut();353*s += 1;354Ok(UpdateDeadline::Continue(1))355}),356|_| {},357)358.await?359);360Ok(())361}362363#[wasmtime_test]364async fn epoch_callback_yield(config: &mut Config) -> Result<()> {365assert_eq!(366Some((1, 1)),367run_and_count_yields_or_trap(368config,369"370(module371(import \"\" \"bump_epoch\" (func $bump))372(func (export \"run\")373call $bump ;; bump epoch374call $subfunc) ;; call func; will notice new epoch and yield375(func $subfunc))376",3771,378InterruptMode::Callback(|mut cx| {379let s = cx.data_mut();380*s += 1;381Ok(UpdateDeadline::Yield(1))382}),383|_| {},384)385.await?386);387388Ok(())389}390391#[wasmtime_test]392async fn epoch_callback_yield_custom(config: &mut Config) -> Result<()> {393assert_eq!(394Some((1, 1)),395run_and_count_yields_or_trap(396config,397"398(module399(import \"\" \"bump_epoch\" (func $bump))400(func (export \"run\")401call $bump ;; bump epoch402call $subfunc) ;; call func; will notice new epoch and yield403(func $subfunc))404",4051,406InterruptMode::Callback(|mut cx| {407let s = cx.data_mut();408*s += 1;409let fut = Box::pin(tokio::task::yield_now());410Ok(UpdateDeadline::YieldCustom(1, fut))411}),412|_| {},413)414.await?415);416Ok(())417}418419#[wasmtime_test]420async fn epoch_callback_trap(config: &mut Config) -> Result<()> {421assert_eq!(422None,423run_and_count_yields_or_trap(424config,425"426(module427(import \"\" \"bump_epoch\" (func $bump))428(func (export \"run\")429call $bump ;; bump epoch430call $subfunc) ;; call func; will notice new epoch and yield431(func $subfunc))432",4331,434InterruptMode::Callback(|_| Err(format_err!("Failing in callback"))),435|_| {},436)437.await?438);439Ok(())440}441442#[wasmtime_test]443async fn drop_future_on_epoch_yield(config: &mut Config) -> Result<()> {444let wasm = "445(module446(import \"\" \"bump_epoch\" (func $bump))447(import \"\" \"im_alive\" (func $im_alive))448(import \"\" \"oops\" (func $oops))449(func (export \"run\")450(call $im_alive)451(call $bump)452(call $subfunc) ;; subfunc entry to do epoch check453(call $oops))454(func $subfunc))455";456457let engine = build_engine(config)?;458let mut linker = make_env::<()>(&engine);459460// Create a few helpers for the Wasm to call.461let alive_flag = Arc::new(AtomicBool::new(false));462let alive_flag_clone = alive_flag.clone();463linker464.func_new(465"",466"oops",467FuncType::new(&engine, None, None),468move |_caller, _params, _results| {469panic!("Should not have reached this point!");470},471)472.unwrap();473linker474.func_new(475"",476"im_alive",477FuncType::new(&engine, None, None),478move |_caller, _params, _results| {479alive_flag_clone.store(true, Ordering::Release);480Ok(())481},482)483.unwrap();484485let module = Module::new(&engine, wasm).unwrap();486let mut store = Store::new(&engine, ());487488store.set_epoch_deadline(1);489store.epoch_deadline_async_yield_and_update(1);490491let instance = linker.instantiate_async(&mut store, &module).await.unwrap();492let f = instance.get_func(&mut store, "run").unwrap();493let _ = PollOnce::new(Box::pin(f.call_async(&mut store, &[], &mut []))).await;494495assert_eq!(true, alive_flag.load(Ordering::Acquire));496Ok(())497}498499500