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"]
task_return_run()19 fn task_return_run();
20 }
21 #[cfg(not(target_arch = "wasm32"))]
task_return_run()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"]
inc_backpressure()30 fn inc_backpressure();
31 #[link_name = "dec-backpressure"]
dec_backpressure()32 fn dec_backpressure();
33 }
34 #[cfg(not(target_arch = "wasm32"))]
inc_backpressure()35 unsafe fn inc_backpressure() {
36 unreachable!()
37 }
38 #[cfg(not(target_arch = "wasm32"))]
dec_backpressure()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"]
yield_times(_: u64) -> u3248 pub fn yield_times(_: u64) -> u32;
49 }
50 #[cfg(not(target_arch = "wasm32"))]
yield_times(_: u64) -> u3251 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"]
yield_times(_: *mut u8) -> u3261 pub fn yield_times(_: *mut u8) -> u32;
62 }
63 #[cfg(not(target_arch = "wasm32"))]
yield_times(_: *mut u8) -> u3264 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")]
export_run(mode: u8, cancel_delay_times: u64) -> u32118 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")]
callback_run(event0: u32, event1: u32, event2: u32) -> u32132 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_times`
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`:
main()440 fn main() {}
441