1 mod bindings { 2 wit_bindgen::generate!({ 3 path: "../misc/component-async-tests/wit", 4 world: "cancel-caller", 5 }); 6 } 7 8 use test_programs::async_::{ 9 BLOCKED, CALLBACK_CODE_EXIT, CALLBACK_CODE_WAIT, EVENT_NONE, EVENT_SUBTASK, 10 STATUS_RETURN_CANCELLED, STATUS_RETURNED, STATUS_START_CANCELLED, STATUS_STARTED, 11 STATUS_STARTING, context_get, context_set, subtask_cancel, subtask_cancel_async, subtask_drop, 12 waitable_join, waitable_set_drop, waitable_set_new, 13 }; 14 15 #[cfg(target_arch = "wasm32")] 16 #[link(wasm_import_module = "[export]local:local/cancel")] 17 unsafe extern "C" { 18 #[link_name = "[task-return]run"] 19 fn task_return_run(); 20 } 21 #[cfg(not(target_arch = "wasm32"))] 22 unsafe extern "C" fn task_return_run() { 23 unreachable!() 24 } 25 26 #[cfg(target_arch = "wasm32")] 27 #[link(wasm_import_module = "local:local/backpressure")] 28 unsafe extern "C" { 29 #[link_name = "inc-backpressure"] 30 fn inc_backpressure(); 31 #[link_name = "dec-backpressure"] 32 fn dec_backpressure(); 33 } 34 #[cfg(not(target_arch = "wasm32"))] 35 unsafe fn inc_backpressure() { 36 unreachable!() 37 } 38 #[cfg(not(target_arch = "wasm32"))] 39 unsafe fn dec_backpressure() { 40 unreachable!() 41 } 42 43 mod yield_ { 44 #[cfg(target_arch = "wasm32")] 45 #[link(wasm_import_module = "local:local/yield")] 46 unsafe extern "C" { 47 #[link_name = "[async-lower]yield-times"] 48 pub fn yield_times(_: u64) -> u32; 49 } 50 #[cfg(not(target_arch = "wasm32"))] 51 pub unsafe fn yield_times(_: u64) -> u32 { 52 unreachable!() 53 } 54 } 55 56 mod yield_with_options { 57 #[cfg(target_arch = "wasm32")] 58 #[link(wasm_import_module = "local:local/yield-with-options")] 59 unsafe extern "C" { 60 #[link_name = "[async-lower]yield-times"] 61 pub fn yield_times(_: *mut u8) -> u32; 62 } 63 #[cfg(not(target_arch = "wasm32"))] 64 pub unsafe fn yield_times(_: *mut u8) -> u32 { 65 unreachable!() 66 } 67 } 68 69 const ON_CANCEL_TASK_RETURN: u8 = 0; 70 const ON_CANCEL_TASK_CANCEL: u8 = 1; 71 72 const _MODE_NORMAL: u8 = 0; 73 const MODE_TRAP_CANCEL_GUEST_AFTER_START_CANCELLED: u8 = 1; 74 const MODE_TRAP_CANCEL_GUEST_AFTER_RETURN_CANCELLED: u8 = 2; 75 const MODE_TRAP_CANCEL_GUEST_AFTER_RETURN: u8 = 3; 76 const _MODE_TRAP_CANCEL_HOST_AFTER_RETURN_CANCELLED: u8 = 4; 77 const _MODE_TRAP_CANCEL_HOST_AFTER_RETURN: u8 = 5; 78 79 #[repr(C)] 80 struct YieldParams { 81 times: u64, 82 on_cancel: u8, 83 on_cancel_delay_times: u64, 84 synchronous_delay: bool, 85 mode: u8, 86 } 87 88 enum State { 89 S0 { 90 mode: u8, 91 cancel_delay_times: u64, 92 }, 93 S1 { 94 mode: u8, 95 set: u32, 96 waitable: u32, 97 params: *mut YieldParams, 98 }, 99 S2 { 100 mode: u8, 101 set: u32, 102 waitable: u32, 103 params: *mut YieldParams, 104 }, 105 S3 { 106 set: u32, 107 waitable: u32, 108 params: *mut YieldParams, 109 }, 110 S4 { 111 set: u32, 112 waitable: u32, 113 params: *mut YieldParams, 114 }, 115 } 116 117 #[unsafe(export_name = "[async-lift]local:local/cancel#run")] 118 unsafe extern "C" fn export_run(mode: u8, cancel_delay_times: u64) -> u32 { 119 unsafe { 120 context_set( 121 u32::try_from(Box::into_raw(Box::new(State::S0 { 122 mode, 123 cancel_delay_times, 124 })) as usize) 125 .unwrap(), 126 ); 127 callback_run(EVENT_NONE, 0, 0) 128 } 129 } 130 131 #[unsafe(export_name = "[callback][async-lift]local:local/cancel#run")] 132 unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 { 133 unsafe { 134 let state = &mut *(usize::try_from(context_get()).unwrap() as *mut State); 135 match state { 136 State::S0 { 137 mode, 138 cancel_delay_times, 139 } => { 140 assert_eq!(event0, EVENT_NONE); 141 142 // First, call and cancel `yield_with_options::yield_tiems` 143 // with backpressure enabled. Cancelling should not block since 144 // the call will not even have started. 145 146 inc_backpressure(); 147 148 let params = Box::into_raw(Box::new(YieldParams { 149 times: 60 * 60 * 1000, 150 on_cancel: ON_CANCEL_TASK_CANCEL, 151 on_cancel_delay_times: 0, 152 synchronous_delay: false, 153 mode: *mode, 154 })); 155 156 let status = yield_with_options::yield_times(params.cast()); 157 158 let waitable = status >> 4; 159 let status = status & 0xF; 160 161 assert_eq!(status, STATUS_STARTING); 162 163 let result = subtask_cancel_async(waitable); 164 165 assert_eq!(result, STATUS_START_CANCELLED); 166 167 if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_START_CANCELLED { 168 // This should trap, since `waitable` has already been 169 // cancelled: 170 subtask_cancel_async(waitable); 171 unreachable!() 172 } 173 174 subtask_drop(waitable); 175 176 // Next, call and cancel `yield_with_options::yield_times` with 177 // backpressure disabled. Cancelling should not block since we 178 // specified zero cancel delay to the callee. 179 180 dec_backpressure(); 181 182 let status = yield_with_options::yield_times(params.cast()); 183 184 let waitable = status >> 4; 185 let status = status & 0xF; 186 187 assert_eq!(status, STATUS_STARTED); 188 189 let result = subtask_cancel_async(waitable); 190 191 assert_eq!(result, STATUS_RETURN_CANCELLED); 192 193 if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_RETURN_CANCELLED { 194 // This should trap, since `waitable` has already been 195 // cancelled: 196 subtask_cancel_async(waitable); 197 unreachable!() 198 } 199 200 subtask_drop(waitable); 201 202 // Next, call and cancel `yield_with_options::yieldtimes` with 203 // a non-zero cancel delay. Cancelling _should_ block this 204 // time. 205 206 (*params).on_cancel_delay_times = *cancel_delay_times; 207 208 let status = yield_with_options::yield_times(params.cast()); 209 210 let waitable = status >> 4; 211 let status = status & 0xF; 212 213 assert_eq!(status, STATUS_STARTED); 214 215 let result = subtask_cancel_async(waitable); 216 217 assert_eq!(result, BLOCKED); 218 219 let set = waitable_set_new(); 220 waitable_join(waitable, set); 221 222 *state = State::S1 { 223 mode: *mode, 224 set, 225 waitable, 226 params, 227 }; 228 229 CALLBACK_CODE_WAIT | (set << 4) 230 } 231 232 State::S1 { 233 mode, 234 set, 235 waitable, 236 params, 237 } => { 238 assert_eq!(event0, EVENT_SUBTASK); 239 assert_eq!(event1, *waitable); 240 assert_eq!(event2, STATUS_RETURN_CANCELLED); 241 242 waitable_join(*waitable, 0); 243 subtask_drop(*waitable); 244 245 // Next, call and cancel `yield_with_options::yield_times` with 246 // a non-zero cancel delay, but this time specifying that the 247 // callee should call `task.return` instead of `task.cancel`. 248 // Cancelling _should_ block this time. 249 250 (**params).on_cancel = ON_CANCEL_TASK_RETURN; 251 252 let status = yield_with_options::yield_times(params.cast()); 253 254 let waitable = status >> 4; 255 let status = status & 0xF; 256 257 assert_eq!(status, STATUS_STARTED); 258 259 let result = subtask_cancel_async(waitable); 260 261 assert_eq!(result, BLOCKED); 262 263 waitable_join(waitable, *set); 264 265 let set = *set; 266 267 *state = State::S2 { 268 mode: *mode, 269 set, 270 waitable, 271 params: *params, 272 }; 273 274 CALLBACK_CODE_WAIT | (set << 4) 275 } 276 277 State::S2 { 278 mode, 279 set, 280 waitable, 281 params, 282 } => { 283 assert_eq!(event0, EVENT_SUBTASK); 284 assert_eq!(event1, *waitable); 285 assert_eq!(event2, STATUS_RETURNED); 286 287 if *mode == MODE_TRAP_CANCEL_GUEST_AFTER_RETURN { 288 // This should trap, since `waitable` has already returned: 289 subtask_cancel_async(*waitable); 290 unreachable!() 291 } 292 293 waitable_join(*waitable, 0); 294 subtask_drop(*waitable); 295 296 // Next, call and cancel `yield_with_options::yield_times` with 297 // a non-zero cancel delay, and specify that the callee should 298 // delay the cancel by making a synchronous call. 299 300 (**params).on_cancel = ON_CANCEL_TASK_CANCEL; 301 (**params).synchronous_delay = true; 302 303 let status = yield_with_options::yield_times(params.cast()); 304 305 let waitable = status >> 4; 306 let status = status & 0xF; 307 308 assert_eq!(status, STATUS_STARTED); 309 310 let result = subtask_cancel_async(waitable); 311 312 // NB: As of this writing, Wasmtime spawns a new fiber for 313 // async->async guest calls, which means the above call should 314 // block asynchronously, giving us back control. However, the 315 // runtime could alternatively execute the call on the original 316 // fiber, in which case the above call would block synchronously 317 // and return `STATUS_RETURN_CANCELLED`. If Wasmtime's behavior 318 // changes, this test will need to be modified. 319 assert_eq!(result, BLOCKED); 320 321 waitable_join(waitable, *set); 322 323 let set = *set; 324 325 *state = State::S3 { 326 set, 327 waitable, 328 params: *params, 329 }; 330 331 CALLBACK_CODE_WAIT | (set << 4) 332 } 333 334 State::S3 { 335 set, 336 waitable, 337 params, 338 } => { 339 assert_eq!(event0, EVENT_SUBTASK); 340 assert_eq!(event1, *waitable); 341 assert_eq!(event2, STATUS_RETURN_CANCELLED); 342 343 waitable_join(*waitable, 0); 344 subtask_drop(*waitable); 345 346 // Next, call and cancel `yield_::yield_times`, which the callee 347 // implements using both an synchronous lift and asynchronous 348 // lower. This should block asynchronously and yield a 349 // `STATUS_RETURNED` when complete since the callee cannot 350 // actually be cancelled. 351 352 let status = yield_::yield_times(10); 353 354 let waitable = status >> 4; 355 let status = status & 0xF; 356 357 assert_eq!(status, STATUS_STARTED); 358 359 let result = subtask_cancel_async(waitable); 360 361 assert_eq!(result, BLOCKED); 362 363 waitable_join(waitable, *set); 364 365 let set = *set; 366 367 *state = State::S4 { 368 set, 369 waitable, 370 params: *params, 371 }; 372 373 CALLBACK_CODE_WAIT | (set << 4) 374 } 375 376 State::S4 { 377 set, 378 waitable, 379 params, 380 } => { 381 assert_eq!(event0, EVENT_SUBTASK); 382 assert_eq!(event1, *waitable); 383 assert_eq!(event2, STATUS_RETURNED); 384 385 waitable_join(*waitable, 0); 386 subtask_drop(*waitable); 387 waitable_set_drop(*set); 388 389 // Next, call and cancel `yield_with_options::yield_times` with 390 // a non-zero cancel delay, and specify that the callee should 391 // delay the cancel by making a synchronous call. Here we make 392 // synchronous call to `subtask.cancel`, which should block 393 // synchronously. 394 395 (**params).synchronous_delay = true; 396 397 let status = yield_with_options::yield_times(params.cast()); 398 399 let waitable = status >> 4; 400 let status = status & 0xF; 401 402 assert_eq!(status, STATUS_STARTED); 403 404 let result = subtask_cancel(waitable); 405 406 assert_eq!(result, STATUS_RETURN_CANCELLED); 407 408 waitable_join(waitable, 0); 409 subtask_drop(waitable); 410 411 // Finally, do the same as above, except specify that the callee 412 // should delay the cancel asynchronously. 413 414 (**params).synchronous_delay = false; 415 416 let status = yield_with_options::yield_times(params.cast()); 417 418 let waitable = status >> 4; 419 let status = status & 0xF; 420 421 assert_eq!(status, STATUS_STARTED); 422 423 let result = subtask_cancel(waitable); 424 425 assert_eq!(result, STATUS_RETURN_CANCELLED); 426 427 waitable_join(waitable, 0); 428 subtask_drop(waitable); 429 drop(Box::from_raw(*params)); 430 431 task_return_run(); 432 433 CALLBACK_CODE_EXIT 434 } 435 } 436 } 437 } 438 439 // Unused function; required since this file is built as a `bin`: 440 fn main() {} 441