Path: blob/main/crates/test-programs/src/bin/async_cancel_caller.rs
1693 views
mod bindings {1wit_bindgen::generate!({2path: "../misc/component-async-tests/wit",3world: "cancel-caller",4});5}67use test_programs::async_::{8BLOCKED, CALLBACK_CODE_EXIT, CALLBACK_CODE_WAIT, EVENT_NONE, EVENT_SUBTASK,9STATUS_RETURN_CANCELLED, STATUS_RETURNED, STATUS_START_CANCELLED, STATUS_STARTED,10STATUS_STARTING, context_get, context_set, subtask_cancel, subtask_cancel_async, subtask_drop,11waitable_join, waitable_set_drop, waitable_set_new,12};1314#[cfg(target_arch = "wasm32")]15#[link(wasm_import_module = "[export]local:local/cancel")]16unsafe extern "C" {17#[link_name = "[task-return]run"]18fn task_return_run();19}20#[cfg(not(target_arch = "wasm32"))]21unsafe extern "C" fn task_return_run() {22unreachable!()23}2425#[cfg(target_arch = "wasm32")]26#[link(wasm_import_module = "local:local/backpressure")]27unsafe extern "C" {28#[link_name = "set-backpressure"]29fn set_backpressure(_: bool);30}31#[cfg(not(target_arch = "wasm32"))]32unsafe fn set_backpressure(_: bool) {33unreachable!()34}3536mod sleep {37#[cfg(target_arch = "wasm32")]38#[link(wasm_import_module = "local:local/sleep")]39unsafe extern "C" {40#[link_name = "[async-lower][async]sleep-millis"]41pub fn sleep_millis(_: u64) -> u32;42}43#[cfg(not(target_arch = "wasm32"))]44pub unsafe fn sleep_millis(_: u64) -> u32 {45unreachable!()46}47}4849mod sleep_with_options {50#[cfg(target_arch = "wasm32")]51#[link(wasm_import_module = "local:local/sleep-with-options")]52unsafe extern "C" {53#[link_name = "[async-lower][async]sleep-millis"]54pub fn sleep_millis(_: *mut u8) -> u32;55}56#[cfg(not(target_arch = "wasm32"))]57pub unsafe fn sleep_millis(_: *mut u8) -> u32 {58unreachable!()59}60}6162const ON_CANCEL_TASK_RETURN: u8 = 0;63const ON_CANCEL_TASK_CANCEL: u8 = 1;6465const _MODE_NORMAL: u8 = 0;66const MODE_TRAP_CANCEL_GUEST_AFTER_START_CANCELLED: u8 = 1;67const MODE_TRAP_CANCEL_GUEST_AFTER_RETURN_CANCELLED: u8 = 2;68const MODE_TRAP_CANCEL_GUEST_AFTER_RETURN: u8 = 3;69const _MODE_TRAP_CANCEL_HOST_AFTER_RETURN_CANCELLED: u8 = 4;70const _MODE_TRAP_CANCEL_HOST_AFTER_RETURN: u8 = 5;7172#[repr(C)]73struct SleepParams {74time_in_millis: u64,75on_cancel: u8,76on_cancel_delay_millis: u64,77synchronous_delay: bool,78mode: u8,79}8081enum State {82S0 {83mode: u8,84cancel_delay_millis: u64,85},86S1 {87mode: u8,88set: u32,89waitable: u32,90params: *mut SleepParams,91},92S2 {93mode: u8,94set: u32,95waitable: u32,96params: *mut SleepParams,97},98S3 {99set: u32,100waitable: u32,101params: *mut SleepParams,102},103S4 {104set: u32,105waitable: u32,106params: *mut SleepParams,107},108}109110#[unsafe(export_name = "[async-lift]local:local/cancel#run")]111unsafe extern "C" fn export_run(mode: u8, cancel_delay_millis: u64) -> u32 {112unsafe {113context_set(114u32::try_from(Box::into_raw(Box::new(State::S0 {115mode,116cancel_delay_millis,117})) as usize)118.unwrap(),119);120callback_run(EVENT_NONE, 0, 0)121}122}123124#[unsafe(export_name = "[callback][async-lift]local:local/cancel#run")]125unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 {126unsafe {127let state = &mut *(usize::try_from(context_get()).unwrap() as *mut State);128match state {129State::S0 {130mode,131cancel_delay_millis,132} => {133assert_eq!(event0, EVENT_NONE);134135// First, call and cancel `sleep_with_options::sleep_millis`136// with backpressure enabled. Cancelling should not block since137// the call will not even have started.138139set_backpressure(true);140141let params = Box::into_raw(Box::new(SleepParams {142time_in_millis: 60 * 60 * 1000,143on_cancel: ON_CANCEL_TASK_CANCEL,144on_cancel_delay_millis: 0,145synchronous_delay: false,146mode: *mode,147}));148149let status = sleep_with_options::sleep_millis(params.cast());150151let waitable = status >> 4;152let status = status & 0xF;153154assert_eq!(status, STATUS_STARTING);155156let result = subtask_cancel_async(waitable);157158assert_eq!(result, STATUS_START_CANCELLED);159160if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_START_CANCELLED {161// This should trap, since `waitable` has already been162// cancelled:163subtask_cancel_async(waitable);164unreachable!()165}166167subtask_drop(waitable);168169// Next, call and cancel `sleep_with_options::sleep_millis` with170// backpressure disabled. Cancelling should not block since we171// specified zero cancel delay to the callee.172173set_backpressure(false);174175let status = sleep_with_options::sleep_millis(params.cast());176177let waitable = status >> 4;178let status = status & 0xF;179180assert_eq!(status, STATUS_STARTED);181182let result = subtask_cancel_async(waitable);183184assert_eq!(result, STATUS_RETURN_CANCELLED);185186if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_RETURN_CANCELLED {187// This should trap, since `waitable` has already been188// cancelled:189subtask_cancel_async(waitable);190unreachable!()191}192193subtask_drop(waitable);194195// Next, call and cancel `sleep_with_options::sleep_millis` with196// a non-zero cancel delay. Cancelling _should_ block this197// time.198199(*params).on_cancel_delay_millis = *cancel_delay_millis;200201let status = sleep_with_options::sleep_millis(params.cast());202203let waitable = status >> 4;204let status = status & 0xF;205206assert_eq!(status, STATUS_STARTED);207208let result = subtask_cancel_async(waitable);209210assert_eq!(result, BLOCKED);211212let set = waitable_set_new();213waitable_join(waitable, set);214215*state = State::S1 {216mode: *mode,217set,218waitable,219params,220};221222CALLBACK_CODE_WAIT | (set << 4)223}224225State::S1 {226mode,227set,228waitable,229params,230} => {231assert_eq!(event0, EVENT_SUBTASK);232assert_eq!(event1, *waitable);233assert_eq!(event2, STATUS_RETURN_CANCELLED);234235waitable_join(*waitable, 0);236subtask_drop(*waitable);237238// Next, call and cancel `sleep_with_options::sleep_millis` with239// a non-zero cancel delay, but this time specifying that the240// callee should call `task.return` instead of `task.cancel`.241// Cancelling _should_ block this time.242243(**params).on_cancel = ON_CANCEL_TASK_RETURN;244245let status = sleep_with_options::sleep_millis(params.cast());246247let waitable = status >> 4;248let status = status & 0xF;249250assert_eq!(status, STATUS_STARTED);251252let result = subtask_cancel_async(waitable);253254assert_eq!(result, BLOCKED);255256waitable_join(waitable, *set);257258let set = *set;259260*state = State::S2 {261mode: *mode,262set,263waitable,264params: *params,265};266267CALLBACK_CODE_WAIT | (set << 4)268}269270State::S2 {271mode,272set,273waitable,274params,275} => {276assert_eq!(event0, EVENT_SUBTASK);277assert_eq!(event1, *waitable);278assert_eq!(event2, STATUS_RETURNED);279280if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_RETURN {281// This should trap, since `waitable` has already returned:282subtask_cancel_async(*waitable);283unreachable!()284}285286waitable_join(*waitable, 0);287subtask_drop(*waitable);288289// Next, call and cancel `sleep_with_options::sleep_millis` with290// a non-zero cancel delay, and specify that the callee should291// delay the cancel by making a synchronous call.292293(**params).on_cancel = ON_CANCEL_TASK_CANCEL;294(**params).synchronous_delay = true;295296let status = sleep_with_options::sleep_millis(params.cast());297298let waitable = status >> 4;299let status = status & 0xF;300301assert_eq!(status, STATUS_STARTED);302303let result = subtask_cancel_async(waitable);304305// NB: As of this writing, Wasmtime spawns a new fiber for306// async->async guest calls, which means the above call should307// block asynchronously, giving us back control. However, the308// runtime could alternatively execute the call on the original309// fiber, in which case the above call would block synchronously310// and return `STATUS_RETURN_CANCELLED`. If Wasmtime's behavior311// changes, this test will need to be modified.312assert_eq!(result, BLOCKED);313314waitable_join(waitable, *set);315316let set = *set;317318*state = State::S3 {319set,320waitable,321params: *params,322};323324CALLBACK_CODE_WAIT | (set << 4)325}326327State::S3 {328set,329waitable,330params,331} => {332assert_eq!(event0, EVENT_SUBTASK);333assert_eq!(event1, *waitable);334assert_eq!(event2, STATUS_RETURN_CANCELLED);335336waitable_join(*waitable, 0);337subtask_drop(*waitable);338339// Next, call and cancel `sleep::sleep_millis`, which the callee340// implements using both an synchronous lift and asynchronous341// lower. This should block asynchronously and yield a342// `STATUS_RETURNED` when complete since the callee cannot343// actually be cancelled.344345let status = sleep::sleep_millis(10);346347let waitable = status >> 4;348let status = status & 0xF;349350assert_eq!(status, STATUS_STARTED);351352let result = subtask_cancel_async(waitable);353354assert_eq!(result, BLOCKED);355356waitable_join(waitable, *set);357358let set = *set;359360*state = State::S4 {361set,362waitable,363params: *params,364};365366CALLBACK_CODE_WAIT | (set << 4)367}368369State::S4 {370set,371waitable,372params,373} => {374assert_eq!(event0, EVENT_SUBTASK);375assert_eq!(event1, *waitable);376assert_eq!(event2, STATUS_RETURNED);377378waitable_join(*waitable, 0);379subtask_drop(*waitable);380waitable_set_drop(*set);381382// Next, call and cancel `sleep_with_options::sleep_millis` with383// a non-zero cancel delay, and specify that the callee should384// delay the cancel by making a synchronous call. Here we make385// synchronous call to `subtask.cancel`, which should block386// synchronously.387388(**params).synchronous_delay = true;389390let status = sleep_with_options::sleep_millis(params.cast());391392let waitable = status >> 4;393let status = status & 0xF;394395assert_eq!(status, STATUS_STARTED);396397let result = subtask_cancel(waitable);398399assert_eq!(result, STATUS_RETURN_CANCELLED);400401waitable_join(waitable, 0);402subtask_drop(waitable);403404// Finally, do the same as above, except specify that the callee405// should delay the cancel asynchronously.406407(**params).synchronous_delay = false;408409let status = sleep_with_options::sleep_millis(params.cast());410411let waitable = status >> 4;412let status = status & 0xF;413414assert_eq!(status, STATUS_STARTED);415416let result = subtask_cancel(waitable);417418assert_eq!(result, STATUS_RETURN_CANCELLED);419420waitable_join(waitable, 0);421subtask_drop(waitable);422drop(Box::from_raw(*params));423424task_return_run();425426CALLBACK_CODE_EXIT427}428}429}430}431432// Unused function; required since this file is built as a `bin`:433fn main() {}434435436