wit_bindgen/rt/async_support.rs
1#![deny(missing_docs)]
2
3use alloc::boxed::Box;
4use alloc::collections::BTreeMap;
5use alloc::sync::Arc;
6use alloc::task::Wake;
7use core::ffi::c_void;
8use core::future::Future;
9use core::mem;
10use core::pin::Pin;
11use core::ptr;
12use core::sync::atomic::{AtomicU32, Ordering};
13use core::task::{Context, Poll, Waker};
14
15macro_rules! rtdebug {
16 ($($f:tt)*) => {
17 // Change this flag to enable debugging, right now we're not using a
18 // crate like `log` or such to reduce runtime deps. Intended to be used
19 // during development for now.
20 if false {
21 #[cfg(feature = "std")]
22 std::eprintln!($($f)*);
23 }
24 }
25}
26
27/// Helper macro to deduplicate foreign definitions of wasm functions.
28///
29/// This automatically imports when on wasm targets and then defines a dummy
30/// panicking shim for native targets to support native compilation but fail at
31/// runtime.
32macro_rules! extern_wasm {
33 (
34 $(#[$extern_attr:meta])*
35 unsafe extern "C" {
36 $(
37 $(#[$func_attr:meta])*
38 $vis:vis fn $func_name:ident ( $($args:tt)* ) $(-> $ret:ty)?;
39 )*
40 }
41 ) => {
42 $(
43 #[cfg(not(target_family = "wasm"))]
44 #[allow(unused, reason = "dummy shim for non-wasm compilation, never invoked")]
45 $vis unsafe fn $func_name($($args)*) $(-> $ret)? {
46 unreachable!();
47 }
48 )*
49
50 #[cfg(target_family = "wasm")]
51 $(#[$extern_attr])*
52 unsafe extern "C" {
53 $(
54 $(#[$func_attr])*
55 $vis fn $func_name($($args)*) $(-> $ret)?;
56 )*
57 }
58 };
59}
60
61mod abi_buffer;
62mod cabi;
63mod error_context;
64mod future_support;
65#[cfg(feature = "futures-stream")]
66mod futures_stream;
67#[cfg(feature = "inter-task-wakeup")]
68mod inter_task_wakeup;
69mod stream_support;
70mod subtask;
71mod try_lock;
72#[cfg(feature = "inter-task-wakeup")]
73mod unit_stream;
74mod waitable;
75mod waitable_set;
76
77#[cfg(not(feature = "inter-task-wakeup"))]
78use inter_task_wakeup_disabled as inter_task_wakeup;
79#[cfg(not(feature = "inter-task-wakeup"))]
80mod inter_task_wakeup_disabled;
81
82use self::waitable_set::WaitableSet;
83pub use abi_buffer::*;
84pub use error_context::*;
85pub use future_support::*;
86#[cfg(feature = "futures-stream")]
87pub use futures_stream::*;
88pub use stream_support::*;
89#[doc(hidden)]
90pub use subtask::Subtask;
91#[cfg(feature = "inter-task-wakeup")]
92pub use unit_stream::*;
93
94type BoxFuture<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
95
96#[cfg(feature = "async-spawn")]
97mod spawn;
98#[cfg(feature = "async-spawn")]
99pub use spawn::spawn;
100#[cfg(not(feature = "async-spawn"))]
101mod spawn_disabled;
102#[cfg(not(feature = "async-spawn"))]
103use spawn_disabled as spawn;
104
105/// Represents a task created by either a call to an async-lifted export or a
106/// future run using `block_on` or `start_task`.
107struct FutureState<'a> {
108 /// Remaining work to do (if any) before this task can be considered "done".
109 ///
110 /// Note that we won't tell the host the task is done until this is drained
111 /// and `waitables` is empty.
112 tasks: spawn::Tasks<'a>,
113
114 /// The waitable set containing waitables created by this task, if any.
115 waitable_set: Option<WaitableSet>,
116
117 /// State of all waitables in `waitable_set`, and the ptr/callback they're
118 /// associated with.
119 //
120 // Note that this is a `BTreeMap` rather than a `HashMap` only because, as
121 // of this writing, initializing the default hasher for `HashMap` requires
122 // calling `wasi_snapshot_preview1:random_get`, which requires initializing
123 // the `wasi_snapshot_preview1` adapter when targeting `wasm32-wasip2` and
124 // later, and that's expensive enough that we'd prefer to avoid it for apps
125 // which otherwise make no use of the adapter.
126 waitables: BTreeMap<u32, (*mut c_void, unsafe extern "C" fn(*mut c_void, u32))>,
127
128 /// Raw structure used to pass to `cabi::wasip3_task_set`
129 wasip3_task: cabi::wasip3_task,
130
131 /// Rust-level state for the waker, notably a bool as to whether this has
132 /// been woken.
133 waker: Arc<FutureWaker>,
134
135 /// Clone of `waker` field, but represented as `std::task::Waker`.
136 waker_clone: Waker,
137
138 /// State related to supporting inter-task wakeup scenarios.
139 inter_task_wakeup: inter_task_wakeup::State,
140}
141
142impl FutureState<'_> {
143 fn new(future: BoxFuture<'_>) -> FutureState<'_> {
144 let waker = Arc::new(FutureWaker::default());
145 FutureState {
146 waker_clone: waker.clone().into(),
147 waker,
148 tasks: spawn::Tasks::new(future),
149 waitable_set: None,
150 waitables: BTreeMap::new(),
151 wasip3_task: cabi::wasip3_task {
152 // This pointer is filled in before calling `wasip3_task_set`.
153 ptr: ptr::null_mut(),
154 version: cabi::WASIP3_TASK_V1,
155 waitable_register,
156 waitable_unregister,
157 },
158 inter_task_wakeup: Default::default(),
159 }
160 }
161
162 fn get_or_create_waitable_set(&mut self) -> &WaitableSet {
163 self.waitable_set.get_or_insert_with(WaitableSet::new)
164 }
165
166 fn add_waitable(&mut self, waitable: u32) {
167 self.get_or_create_waitable_set().join(waitable)
168 }
169
170 fn remove_waitable(&mut self, waitable: u32) {
171 WaitableSet::remove_waitable_from_all_sets(waitable)
172 }
173
174 fn remaining_work(&self) -> bool {
175 !self.waitables.is_empty()
176 }
177
178 /// Handles the `event{0,1,2}` event codes and returns a corresponding
179 /// return code along with a flag whether this future is "done" or not.
180 fn callback(&mut self, event0: u32, event1: u32, event2: u32) -> CallbackCode {
181 match event0 {
182 EVENT_NONE => rtdebug!("EVENT_NONE"),
183 EVENT_SUBTASK => rtdebug!("EVENT_SUBTASK({event1:#x}, {event2:#x})"),
184 EVENT_STREAM_READ => rtdebug!("EVENT_STREAM_READ({event1:#x}, {event2:#x})"),
185 EVENT_STREAM_WRITE => rtdebug!("EVENT_STREAM_WRITE({event1:#x}, {event2:#x})"),
186 EVENT_FUTURE_READ => rtdebug!("EVENT_FUTURE_READ({event1:#x}, {event2:#x})"),
187 EVENT_FUTURE_WRITE => rtdebug!("EVENT_FUTURE_WRITE({event1:#x}, {event2:#x})"),
188 EVENT_CANCEL => {
189 rtdebug!("EVENT_CANCEL");
190
191 // Cancellation is mapped to destruction in Rust, so return a
192 // code/bool indicating we're done. The caller will then
193 // appropriately deallocate this `FutureState` which will
194 // transitively run all destructors.
195 return CallbackCode::Exit;
196 }
197 _ => unreachable!(),
198 }
199
200 self.with_p3_task_set(|me| {
201 // Transition our sleep state to ensure that the inter-task stream
202 // isn't used since there's no need to use that here.
203 me.waker
204 .sleep_state
205 .store(SLEEP_STATE_WOKEN, Ordering::Relaxed);
206
207 // With all of our context now configured, deliver the event
208 // notification this callback corresponds to.
209 //
210 // Note that this should happen under the reset of
211 // `waker.sleep_state` above to ensure that if a waker is woken it
212 // won't actually signal our inter-task stream since we're already
213 // in the process of handling the future.
214 if event0 != EVENT_NONE {
215 me.deliver_waitable_event(event1, event2)
216 }
217
218 // If there's still an in-progress read (e.g. `event{1,2}`) wasn't
219 // ourselves getting woken up, then cancel the read since we're
220 // processing the future here anyway.
221 me.cancel_inter_task_stream_read();
222
223 loop {
224 let mut context = Context::from_waker(&me.waker_clone);
225
226 // On each turn of this loop reset the state to "polling"
227 // which clears out any pending wakeup if one was sent. This
228 // in theory helps minimize wakeups from previous iterations
229 // happening in this iteration.
230 me.waker
231 .sleep_state
232 .store(SLEEP_STATE_POLLING, Ordering::Relaxed);
233
234 // Poll our future, seeing if it was able to make progress.
235 let poll = me.tasks.poll_next(&mut context);
236
237 match poll {
238 // A future completed, yay! Keep going to see if more have
239 // completed.
240 Poll::Ready(Some(())) => (),
241
242 // The task list is empty, but there might be remaining work
243 // in terms of waitables through the cabi interface. In this
244 // situation wait for all waitables to be resolved before
245 // signaling that our own task is done.
246 Poll::Ready(None) => {
247 assert!(me.tasks.is_empty());
248 if me.remaining_work() {
249 let waitable = me.waitable_set.as_ref().unwrap().as_raw();
250 break CallbackCode::Wait(waitable);
251 } else {
252 break CallbackCode::Exit;
253 }
254 }
255
256 // Some future within `self.tasks` is not ready yet. If our
257 // `waker` was signaled then that means this is a yield
258 // operation, otherwise it means we're blocking on
259 // something.
260 Poll::Pending => {
261 assert!(!me.tasks.is_empty());
262 if me.waker.sleep_state.load(Ordering::Relaxed) == SLEEP_STATE_WOKEN {
263 if me.remaining_work() {
264 let (event0, event1, event2) =
265 me.waitable_set.as_ref().unwrap().poll();
266 if event0 != EVENT_NONE {
267 me.deliver_waitable_event(event1, event2);
268 continue;
269 }
270 }
271 break CallbackCode::Yield;
272 }
273
274 // Transition our state to "sleeping" so wakeup
275 // notifications know that they need to signal the
276 // inter-task stream.
277 me.waker
278 .sleep_state
279 .store(SLEEP_STATE_SLEEPING, Ordering::Relaxed);
280 me.read_inter_task_stream();
281 let waitable = me.waitable_set.as_ref().unwrap().as_raw();
282 break CallbackCode::Wait(waitable);
283 }
284 }
285 }
286 })
287 }
288
289 /// Deliver the `code` event to the `waitable` store within our map. This
290 /// waitable should be present because it's part of the waitable set which
291 /// is kept in-sync with our map.
292 fn deliver_waitable_event(&mut self, waitable: u32, code: u32) {
293 self.remove_waitable(waitable);
294
295 if self
296 .inter_task_wakeup
297 .consume_waitable_event(waitable, code)
298 {
299 return;
300 }
301
302 let (ptr, callback) = self.waitables.remove(&waitable).unwrap();
303 unsafe {
304 callback(ptr, code);
305 }
306 }
307
308 fn with_p3_task_set<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
309 // Finish our `wasip3_task` by initializing its self-referential pointer,
310 // and then register it for the duration of this function with
311 // `wasip3_task_set`. The previous value of `wasip3_task_set` will get
312 // restored when this function returns.
313 struct ResetTask(*mut cabi::wasip3_task);
314 impl Drop for ResetTask {
315 fn drop(&mut self) {
316 unsafe {
317 cabi::wasip3_task_set(self.0);
318 }
319 }
320 }
321 let self_raw = self as *mut FutureState<'_>;
322 self.wasip3_task.ptr = self_raw.cast();
323 let prev = unsafe { cabi::wasip3_task_set(&mut self.wasip3_task) };
324 let _reset = ResetTask(prev);
325
326 f(self)
327 }
328}
329
330impl Drop for FutureState<'_> {
331 fn drop(&mut self) {
332 // If there's an active read of the inter-task stream, go ahead and
333 // cancel it, since we're about to drop the stream anyway.
334 self.cancel_inter_task_stream_read();
335
336 // If this state has active tasks then they need to be dropped which may
337 // execute arbitrary code. This arbitrary code might require the p3 APIs
338 // for managing waitables, notably around removing them. In this
339 // situation we ensure that the p3 task is set while futures are being
340 // destroyed.
341 if !self.tasks.is_empty() {
342 self.with_p3_task_set(|me| {
343 me.tasks = Default::default();
344 })
345 }
346 }
347}
348
349unsafe extern "C" fn waitable_register(
350 ptr: *mut c_void,
351 waitable: u32,
352 callback: unsafe extern "C" fn(*mut c_void, u32),
353 callback_ptr: *mut c_void,
354) -> *mut c_void {
355 let ptr = ptr.cast::<FutureState<'static>>();
356 assert!(!ptr.is_null());
357 unsafe {
358 (*ptr).add_waitable(waitable);
359 match (*ptr).waitables.insert(waitable, (callback_ptr, callback)) {
360 Some((prev, _)) => prev,
361 None => ptr::null_mut(),
362 }
363 }
364}
365
366unsafe extern "C" fn waitable_unregister(ptr: *mut c_void, waitable: u32) -> *mut c_void {
367 let ptr = ptr.cast::<FutureState<'static>>();
368 assert!(!ptr.is_null());
369 unsafe {
370 (*ptr).remove_waitable(waitable);
371 match (*ptr).waitables.remove(&waitable) {
372 Some((prev, _)) => prev,
373 None => ptr::null_mut(),
374 }
375 }
376}
377
378/// Status for "this task is actively being polled"
379const SLEEP_STATE_POLLING: u32 = 0;
380/// Status for "this task has a wakeup scheduled, no more action need be taken".
381const SLEEP_STATE_WOKEN: u32 = 1;
382/// Status for "this task is not being polled and has not been woken"
383///
384/// Wakeups on this status signal the inter-task stream.
385const SLEEP_STATE_SLEEPING: u32 = 2;
386
387#[derive(Default)]
388struct FutureWaker {
389 /// One of `SLEEP_STATE_*` indicating the current status.
390 sleep_state: AtomicU32,
391 inter_task_stream: inter_task_wakeup::WakerState,
392}
393
394impl Wake for FutureWaker {
395 fn wake(self: Arc<Self>) {
396 Self::wake_by_ref(&self)
397 }
398
399 fn wake_by_ref(self: &Arc<Self>) {
400 match self.sleep_state.swap(SLEEP_STATE_WOKEN, Ordering::Relaxed) {
401 // If this future was currently being polled, or if someone else
402 // already woke it up, then there's nothing to do.
403 SLEEP_STATE_POLLING | SLEEP_STATE_WOKEN => {}
404
405 // If this future is sleeping, however, then this is a cross-task
406 // wakeup meaning that we need to write to its wakeup stream.
407 other => {
408 assert_eq!(other, SLEEP_STATE_SLEEPING);
409 self.inter_task_stream.wake();
410 }
411 }
412 }
413}
414
415const EVENT_NONE: u32 = 0;
416const EVENT_SUBTASK: u32 = 1;
417const EVENT_STREAM_READ: u32 = 2;
418const EVENT_STREAM_WRITE: u32 = 3;
419const EVENT_FUTURE_READ: u32 = 4;
420const EVENT_FUTURE_WRITE: u32 = 5;
421const EVENT_CANCEL: u32 = 6;
422
423#[derive(PartialEq, Debug)]
424enum CallbackCode {
425 Exit,
426 Yield,
427 Wait(u32),
428}
429
430impl CallbackCode {
431 fn encode(self) -> u32 {
432 match self {
433 CallbackCode::Exit => 0,
434 CallbackCode::Yield => 1,
435 CallbackCode::Wait(waitable) => 2 | (waitable << 4),
436 }
437 }
438}
439
440const STATUS_STARTING: u32 = 0;
441const STATUS_STARTED: u32 = 1;
442const STATUS_RETURNED: u32 = 2;
443const STATUS_STARTED_CANCELLED: u32 = 3;
444const STATUS_RETURNED_CANCELLED: u32 = 4;
445
446const BLOCKED: u32 = 0xffff_ffff;
447const COMPLETED: u32 = 0x0;
448const DROPPED: u32 = 0x1;
449const CANCELLED: u32 = 0x2;
450
451/// Return code of stream/future operations.
452#[derive(PartialEq, Debug, Copy, Clone)]
453enum ReturnCode {
454 /// The operation is blocked and has not completed.
455 Blocked,
456 /// The operation completed with the specified number of items.
457 Completed(u32),
458 /// The other end is dropped, but before that the specified number of items
459 /// were transferred.
460 Dropped(u32),
461 /// The operation was cancelled, but before that the specified number of
462 /// items were transferred.
463 Cancelled(u32),
464}
465
466impl ReturnCode {
467 fn decode(val: u32) -> ReturnCode {
468 if val == BLOCKED {
469 return ReturnCode::Blocked;
470 }
471 let amt = val >> 4;
472 match val & 0xf {
473 COMPLETED => ReturnCode::Completed(amt),
474 DROPPED => ReturnCode::Dropped(amt),
475 CANCELLED => ReturnCode::Cancelled(amt),
476 _ => panic!("unknown return code {val:#x}"),
477 }
478 }
479}
480
481/// Starts execution of the `task` provided, an asynchronous computation.
482///
483/// This is used for async-lifted exports at their definition site. The
484/// representation of the export is `task` and this function is called from the
485/// entrypoint. The code returned here is the same as the callback associated
486/// with this export, and the callback will be used if this task doesn't exit
487/// immediately with its result.
488#[doc(hidden)]
489pub fn start_task(task: impl Future<Output = ()> + 'static) -> i32 {
490 // Allocate a new `FutureState` which will track all state necessary for
491 // our exported task.
492 let state = Box::into_raw(Box::new(FutureState::new(Box::pin(task))));
493
494 // Store our `FutureState` into our context-local-storage slot and then
495 // pretend we got EVENT_NONE to kick off everything.
496 //
497 // SAFETY: we should own `context.set` as we're the root level exported
498 // task, and then `callback` is only invoked when context-local storage is
499 // valid.
500 unsafe {
501 assert!(context_get().is_null());
502 context_set(state.cast());
503 callback(EVENT_NONE, 0, 0) as i32
504 }
505}
506
507/// Handle a progress notification from the host regarding either a call to an
508/// async-lowered import or a stream/future read/write operation.
509///
510/// # Unsafety
511///
512/// This function assumes that `context_get()` returns a `FutureState`.
513#[doc(hidden)]
514pub unsafe fn callback(event0: u32, event1: u32, event2: u32) -> u32 {
515 // Acquire our context-local state, assert it's not-null, and then reset
516 // the state to null while we're running to help prevent any unintended
517 // usage.
518 let state = context_get().cast::<FutureState<'static>>();
519 assert!(!state.is_null());
520 unsafe {
521 context_set(ptr::null_mut());
522 }
523
524 // Use `state` to run the `callback` function in the context of our event
525 // codes we received. If the callback decides to exit then we're done with
526 // our future so deallocate it. Otherwise put our future back in
527 // context-local storage and forward the code.
528 unsafe {
529 let rc = (*state).callback(event0, event1, event2);
530 if rc == CallbackCode::Exit {
531 drop(Box::from_raw(state));
532 } else {
533 context_set(state.cast());
534 }
535 rtdebug!(" => (cb) {rc:?}");
536 rc.encode()
537 }
538}
539
540/// Run the specified future to completion, returning the result.
541///
542/// This uses `waitable-set.wait` to poll for progress on any in-progress calls
543/// to async-lowered imports as necessary.
544// TODO: refactor so `'static` bounds aren't necessary
545pub fn block_on<T: 'static>(future: impl Future<Output = T>) -> T {
546 let mut result = None;
547 let mut state = FutureState::new(Box::pin(async {
548 result = Some(future.await);
549 }));
550 let mut event = (EVENT_NONE, 0, 0);
551 loop {
552 match state.callback(event.0, event.1, event.2) {
553 CallbackCode::Exit => {
554 drop(state);
555 break result.unwrap();
556 }
557 CallbackCode::Yield => event = state.waitable_set.as_ref().unwrap().poll(),
558 CallbackCode::Wait(_) => event = state.waitable_set.as_ref().unwrap().wait(),
559 }
560 }
561}
562
563/// Call the `yield` canonical built-in function.
564///
565/// This yields control to the host temporarily, allowing other tasks to make
566/// progress. It's a good idea to call this inside a busy loop which does not
567/// otherwise ever yield control the host.
568///
569/// Note that this function is a blocking function, not an `async` function.
570/// That means that this is not an async yield which allows other tasks in this
571/// component to progress, but instead this will block the current function
572/// until the host gets back around to returning from this yield. Asynchronous
573/// functions should probably use [`yield_async`] instead.
574///
575/// # Return Value
576///
577/// This function returns a `bool` which indicates whether execution should
578/// continue after this yield point. A return value of `true` means that the
579/// task was not cancelled and execution should continue. A return value of
580/// `false`, however, means that the task was cancelled while it was suspended
581/// at this yield point. The caller should return back and exit from the task
582/// ASAP in this situation.
583pub fn yield_blocking() -> bool {
584 extern_wasm! {
585 #[link(wasm_import_module = "$root")]
586 unsafe extern "C" {
587 #[link_name = "[thread-yield]"]
588 fn yield_() -> bool;
589 }
590 }
591
592 // Note that the return value from the raw intrinsic is inverted, the
593 // canonical ABI returns "did this task get cancelled" while this function
594 // works as "should work continue going".
595 unsafe { !yield_() }
596}
597
598/// The asynchronous counterpart to [`yield_blocking`].
599///
600/// This function does not block the current task but instead gives the
601/// Rust-level executor a chance to yield control back to the host temporarily.
602/// This means that other Rust-level tasks may also be able to progress during
603/// this yield operation.
604///
605/// # Return Value
606///
607/// Unlike [`yield_blocking`] this function does not return anything. If this
608/// component task is cancelled while paused at this yield point then the future
609/// will be dropped and a Rust-level destructor will take over and clean up the
610/// task. It's not necessary to do anything with the return value of this
611/// function other than ensuring that you `.await` the function call.
612pub async fn yield_async() {
613 #[derive(Default)]
614 struct Yield {
615 yielded: bool,
616 }
617
618 impl Future for Yield {
619 type Output = ();
620
621 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
622 if self.yielded {
623 Poll::Ready(())
624 } else {
625 self.yielded = true;
626 context.waker().wake_by_ref();
627 Poll::Pending
628 }
629 }
630 }
631
632 Yield::default().await;
633}
634
635/// Call the `backpressure.inc` canonical built-in function.
636pub fn backpressure_inc() {
637 extern_wasm! {
638 #[link(wasm_import_module = "$root")]
639 unsafe extern "C" {
640 #[link_name = "[backpressure-inc]"]
641 fn backpressure_inc();
642 }
643 }
644
645 unsafe { backpressure_inc() }
646}
647
648/// Call the `backpressure.dec` canonical built-in function.
649pub fn backpressure_dec() {
650 extern_wasm! {
651 #[link(wasm_import_module = "$root")]
652 unsafe extern "C" {
653 #[link_name = "[backpressure-dec]"]
654 fn backpressure_dec();
655 }
656 }
657
658 unsafe { backpressure_dec() }
659}
660
661fn context_get() -> *mut u8 {
662 extern_wasm! {
663 #[link(wasm_import_module = "$root")]
664 unsafe extern "C" {
665 #[link_name = "[context-get-0]"]
666 fn get() -> *mut u8;
667 }
668 }
669
670 unsafe { get() }
671}
672
673unsafe fn context_set(value: *mut u8) {
674 extern_wasm! {
675 #[link(wasm_import_module = "$root")]
676 unsafe extern "C" {
677 #[link_name = "[context-set-0]"]
678 fn set(value: *mut u8);
679 }
680 }
681
682 unsafe { set(value) }
683}
684
685#[doc(hidden)]
686pub struct TaskCancelOnDrop {
687 _priv: (),
688}
689
690impl TaskCancelOnDrop {
691 #[doc(hidden)]
692 pub fn new() -> TaskCancelOnDrop {
693 TaskCancelOnDrop { _priv: () }
694 }
695
696 #[doc(hidden)]
697 pub fn forget(self) {
698 mem::forget(self);
699 }
700}
701
702impl Drop for TaskCancelOnDrop {
703 fn drop(&mut self) {
704 extern_wasm! {
705 #[link(wasm_import_module = "[export]$root")]
706 unsafe extern "C" {
707 #[link_name = "[task-cancel]"]
708 fn cancel();
709 }
710 }
711
712 unsafe { cancel() }
713 }
714}