Path: blob/main/crates/test-programs/src/bin/async_cancel_caller.rs
3072 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 = "inc-backpressure"]29fn inc_backpressure();30#[link_name = "dec-backpressure"]31fn dec_backpressure();32}33#[cfg(not(target_arch = "wasm32"))]34unsafe fn inc_backpressure() {35unreachable!()36}37#[cfg(not(target_arch = "wasm32"))]38unsafe fn dec_backpressure() {39unreachable!()40}4142mod sleep {43#[cfg(target_arch = "wasm32")]44#[link(wasm_import_module = "local:local/sleep")]45unsafe extern "C" {46#[link_name = "[async-lower]sleep-millis"]47pub fn sleep_millis(_: u64) -> u32;48}49#[cfg(not(target_arch = "wasm32"))]50pub unsafe fn sleep_millis(_: u64) -> u32 {51unreachable!()52}53}5455mod sleep_with_options {56#[cfg(target_arch = "wasm32")]57#[link(wasm_import_module = "local:local/sleep-with-options")]58unsafe extern "C" {59#[link_name = "[async-lower]sleep-millis"]60pub fn sleep_millis(_: *mut u8) -> u32;61}62#[cfg(not(target_arch = "wasm32"))]63pub unsafe fn sleep_millis(_: *mut u8) -> u32 {64unreachable!()65}66}6768const ON_CANCEL_TASK_RETURN: u8 = 0;69const ON_CANCEL_TASK_CANCEL: u8 = 1;7071const _MODE_NORMAL: u8 = 0;72const MODE_TRAP_CANCEL_GUEST_AFTER_START_CANCELLED: u8 = 1;73const MODE_TRAP_CANCEL_GUEST_AFTER_RETURN_CANCELLED: u8 = 2;74const MODE_TRAP_CANCEL_GUEST_AFTER_RETURN: u8 = 3;75const _MODE_TRAP_CANCEL_HOST_AFTER_RETURN_CANCELLED: u8 = 4;76const _MODE_TRAP_CANCEL_HOST_AFTER_RETURN: u8 = 5;7778#[repr(C)]79struct SleepParams {80time_in_millis: u64,81on_cancel: u8,82on_cancel_delay_millis: u64,83synchronous_delay: bool,84mode: u8,85}8687enum State {88S0 {89mode: u8,90cancel_delay_millis: u64,91},92S1 {93mode: u8,94set: u32,95waitable: u32,96params: *mut SleepParams,97},98S2 {99mode: u8,100set: u32,101waitable: u32,102params: *mut SleepParams,103},104S3 {105set: u32,106waitable: u32,107params: *mut SleepParams,108},109S4 {110set: u32,111waitable: u32,112params: *mut SleepParams,113},114}115116#[unsafe(export_name = "[async-lift]local:local/cancel#run")]117unsafe extern "C" fn export_run(mode: u8, cancel_delay_millis: u64) -> u32 {118unsafe {119context_set(120u32::try_from(Box::into_raw(Box::new(State::S0 {121mode,122cancel_delay_millis,123})) as usize)124.unwrap(),125);126callback_run(EVENT_NONE, 0, 0)127}128}129130#[unsafe(export_name = "[callback][async-lift]local:local/cancel#run")]131unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 {132unsafe {133let state = &mut *(usize::try_from(context_get()).unwrap() as *mut State);134match state {135State::S0 {136mode,137cancel_delay_millis,138} => {139assert_eq!(event0, EVENT_NONE);140141// First, call and cancel `sleep_with_options::sleep_millis`142// with backpressure enabled. Cancelling should not block since143// the call will not even have started.144145inc_backpressure();146147let params = Box::into_raw(Box::new(SleepParams {148time_in_millis: 60 * 60 * 1000,149on_cancel: ON_CANCEL_TASK_CANCEL,150on_cancel_delay_millis: 0,151synchronous_delay: false,152mode: *mode,153}));154155let status = sleep_with_options::sleep_millis(params.cast());156157let waitable = status >> 4;158let status = status & 0xF;159160assert_eq!(status, STATUS_STARTING);161162let result = subtask_cancel_async(waitable);163164assert_eq!(result, STATUS_START_CANCELLED);165166if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_START_CANCELLED {167// This should trap, since `waitable` has already been168// cancelled:169subtask_cancel_async(waitable);170unreachable!()171}172173subtask_drop(waitable);174175// Next, call and cancel `sleep_with_options::sleep_millis` with176// backpressure disabled. Cancelling should not block since we177// specified zero cancel delay to the callee.178179dec_backpressure();180181let status = sleep_with_options::sleep_millis(params.cast());182183let waitable = status >> 4;184let status = status & 0xF;185186assert_eq!(status, STATUS_STARTED);187188let result = subtask_cancel_async(waitable);189190assert_eq!(result, STATUS_RETURN_CANCELLED);191192if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_RETURN_CANCELLED {193// This should trap, since `waitable` has already been194// cancelled:195subtask_cancel_async(waitable);196unreachable!()197}198199subtask_drop(waitable);200201// Next, call and cancel `sleep_with_options::sleep_millis` with202// a non-zero cancel delay. Cancelling _should_ block this203// time.204205(*params).on_cancel_delay_millis = *cancel_delay_millis;206207let status = sleep_with_options::sleep_millis(params.cast());208209let waitable = status >> 4;210let status = status & 0xF;211212assert_eq!(status, STATUS_STARTED);213214let result = subtask_cancel_async(waitable);215216assert_eq!(result, BLOCKED);217218let set = waitable_set_new();219waitable_join(waitable, set);220221*state = State::S1 {222mode: *mode,223set,224waitable,225params,226};227228CALLBACK_CODE_WAIT | (set << 4)229}230231State::S1 {232mode,233set,234waitable,235params,236} => {237assert_eq!(event0, EVENT_SUBTASK);238assert_eq!(event1, *waitable);239assert_eq!(event2, STATUS_RETURN_CANCELLED);240241waitable_join(*waitable, 0);242subtask_drop(*waitable);243244// Next, call and cancel `sleep_with_options::sleep_millis` with245// a non-zero cancel delay, but this time specifying that the246// callee should call `task.return` instead of `task.cancel`.247// Cancelling _should_ block this time.248249(**params).on_cancel = ON_CANCEL_TASK_RETURN;250251let status = sleep_with_options::sleep_millis(params.cast());252253let waitable = status >> 4;254let status = status & 0xF;255256assert_eq!(status, STATUS_STARTED);257258let result = subtask_cancel_async(waitable);259260assert_eq!(result, BLOCKED);261262waitable_join(waitable, *set);263264let set = *set;265266*state = State::S2 {267mode: *mode,268set,269waitable,270params: *params,271};272273CALLBACK_CODE_WAIT | (set << 4)274}275276State::S2 {277mode,278set,279waitable,280params,281} => {282assert_eq!(event0, EVENT_SUBTASK);283assert_eq!(event1, *waitable);284assert_eq!(event2, STATUS_RETURNED);285286if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_RETURN {287// This should trap, since `waitable` has already returned:288subtask_cancel_async(*waitable);289unreachable!()290}291292waitable_join(*waitable, 0);293subtask_drop(*waitable);294295// Next, call and cancel `sleep_with_options::sleep_millis` with296// a non-zero cancel delay, and specify that the callee should297// delay the cancel by making a synchronous call.298299(**params).on_cancel = ON_CANCEL_TASK_CANCEL;300(**params).synchronous_delay = true;301302let status = sleep_with_options::sleep_millis(params.cast());303304let waitable = status >> 4;305let status = status & 0xF;306307assert_eq!(status, STATUS_STARTED);308309let result = subtask_cancel_async(waitable);310311// NB: As of this writing, Wasmtime spawns a new fiber for312// async->async guest calls, which means the above call should313// block asynchronously, giving us back control. However, the314// runtime could alternatively execute the call on the original315// fiber, in which case the above call would block synchronously316// and return `STATUS_RETURN_CANCELLED`. If Wasmtime's behavior317// changes, this test will need to be modified.318assert_eq!(result, BLOCKED);319320waitable_join(waitable, *set);321322let set = *set;323324*state = State::S3 {325set,326waitable,327params: *params,328};329330CALLBACK_CODE_WAIT | (set << 4)331}332333State::S3 {334set,335waitable,336params,337} => {338assert_eq!(event0, EVENT_SUBTASK);339assert_eq!(event1, *waitable);340assert_eq!(event2, STATUS_RETURN_CANCELLED);341342waitable_join(*waitable, 0);343subtask_drop(*waitable);344345// Next, call and cancel `sleep::sleep_millis`, which the callee346// implements using both an synchronous lift and asynchronous347// lower. This should block asynchronously and yield a348// `STATUS_RETURNED` when complete since the callee cannot349// actually be cancelled.350351let status = sleep::sleep_millis(10);352353let waitable = status >> 4;354let status = status & 0xF;355356assert_eq!(status, STATUS_STARTED);357358let result = subtask_cancel_async(waitable);359360assert_eq!(result, BLOCKED);361362waitable_join(waitable, *set);363364let set = *set;365366*state = State::S4 {367set,368waitable,369params: *params,370};371372CALLBACK_CODE_WAIT | (set << 4)373}374375State::S4 {376set,377waitable,378params,379} => {380assert_eq!(event0, EVENT_SUBTASK);381assert_eq!(event1, *waitable);382assert_eq!(event2, STATUS_RETURNED);383384waitable_join(*waitable, 0);385subtask_drop(*waitable);386waitable_set_drop(*set);387388// Next, call and cancel `sleep_with_options::sleep_millis` with389// a non-zero cancel delay, and specify that the callee should390// delay the cancel by making a synchronous call. Here we make391// synchronous call to `subtask.cancel`, which should block392// synchronously.393394(**params).synchronous_delay = true;395396let status = sleep_with_options::sleep_millis(params.cast());397398let waitable = status >> 4;399let status = status & 0xF;400401assert_eq!(status, STATUS_STARTED);402403let result = subtask_cancel(waitable);404405assert_eq!(result, STATUS_RETURN_CANCELLED);406407waitable_join(waitable, 0);408subtask_drop(waitable);409410// Finally, do the same as above, except specify that the callee411// should delay the cancel asynchronously.412413(**params).synchronous_delay = false;414415let status = sleep_with_options::sleep_millis(params.cast());416417let waitable = status >> 4;418let status = status & 0xF;419420assert_eq!(status, STATUS_STARTED);421422let result = subtask_cancel(waitable);423424assert_eq!(result, STATUS_RETURN_CANCELLED);425426waitable_join(waitable, 0);427subtask_drop(waitable);428drop(Box::from_raw(*params));429430task_return_run();431432CALLBACK_CODE_EXIT433}434}435}436}437438// Unused function; required since this file is built as a `bin`:439fn main() {}440441442