Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/test-programs/src/bin/async_cancel_caller.rs
3072 views
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 sleep {
44
#[cfg(target_arch = "wasm32")]
45
#[link(wasm_import_module = "local:local/sleep")]
46
unsafe extern "C" {
47
#[link_name = "[async-lower]sleep-millis"]
48
pub fn sleep_millis(_: u64) -> u32;
49
}
50
#[cfg(not(target_arch = "wasm32"))]
51
pub unsafe fn sleep_millis(_: u64) -> u32 {
52
unreachable!()
53
}
54
}
55
56
mod sleep_with_options {
57
#[cfg(target_arch = "wasm32")]
58
#[link(wasm_import_module = "local:local/sleep-with-options")]
59
unsafe extern "C" {
60
#[link_name = "[async-lower]sleep-millis"]
61
pub fn sleep_millis(_: *mut u8) -> u32;
62
}
63
#[cfg(not(target_arch = "wasm32"))]
64
pub unsafe fn sleep_millis(_: *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 SleepParams {
81
time_in_millis: u64,
82
on_cancel: u8,
83
on_cancel_delay_millis: u64,
84
synchronous_delay: bool,
85
mode: u8,
86
}
87
88
enum State {
89
S0 {
90
mode: u8,
91
cancel_delay_millis: u64,
92
},
93
S1 {
94
mode: u8,
95
set: u32,
96
waitable: u32,
97
params: *mut SleepParams,
98
},
99
S2 {
100
mode: u8,
101
set: u32,
102
waitable: u32,
103
params: *mut SleepParams,
104
},
105
S3 {
106
set: u32,
107
waitable: u32,
108
params: *mut SleepParams,
109
},
110
S4 {
111
set: u32,
112
waitable: u32,
113
params: *mut SleepParams,
114
},
115
}
116
117
#[unsafe(export_name = "[async-lift]local:local/cancel#run")]
118
unsafe extern "C" fn export_run(mode: u8, cancel_delay_millis: u64) -> u32 {
119
unsafe {
120
context_set(
121
u32::try_from(Box::into_raw(Box::new(State::S0 {
122
mode,
123
cancel_delay_millis,
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_millis,
139
} => {
140
assert_eq!(event0, EVENT_NONE);
141
142
// First, call and cancel `sleep_with_options::sleep_millis`
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(SleepParams {
149
time_in_millis: 60 * 60 * 1000,
150
on_cancel: ON_CANCEL_TASK_CANCEL,
151
on_cancel_delay_millis: 0,
152
synchronous_delay: false,
153
mode: *mode,
154
}));
155
156
let status = sleep_with_options::sleep_millis(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 `sleep_with_options::sleep_millis` 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 = sleep_with_options::sleep_millis(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 `sleep_with_options::sleep_millis` with
203
// a non-zero cancel delay. Cancelling _should_ block this
204
// time.
205
206
(*params).on_cancel_delay_millis = *cancel_delay_millis;
207
208
let status = sleep_with_options::sleep_millis(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 `sleep_with_options::sleep_millis` 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 = sleep_with_options::sleep_millis(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 `sleep_with_options::sleep_millis` 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 = sleep_with_options::sleep_millis(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 `sleep::sleep_millis`, 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 = sleep::sleep_millis(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 `sleep_with_options::sleep_millis` 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 = sleep_with_options::sleep_millis(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 = sleep_with_options::sleep_millis(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
442