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