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