ic_cdk_executor/lib.rs
1//! An async executor for [`ic-cdk`](https://docs.rs/ic-cdk). Most users should not use this crate directly.
2
3use std::cell::{Cell, RefCell};
4use std::future::Future;
5use std::pin::Pin;
6use std::rc::Rc;
7use std::sync::atomic::AtomicBool;
8use std::task::Context;
9
10use self::waker::WakerState;
11
12/// Must be called on every top-level future corresponding to a method call of a
13/// canister by the IC, other than async functions marked `#[update]` or similar.
14#[cfg_attr(not(target_arch = "wasm32"), allow(unused_variables, unreachable_code))]
15pub fn spawn<F: 'static + Future<Output = ()>>(future: F) {
16 #[cfg(not(target_arch = "wasm32"))]
17 panic!("Cannot be run outside of wasm!"); // really, just cannot be run in a multi-threaded environment
18 let pinned_future = Box::pin(future);
19 let waker_state = Rc::new(WakerState {
20 future: RefCell::new(pinned_future),
21 previous_trap: Cell::new(false),
22 });
23 let waker = waker::waker(Rc::clone(&waker_state));
24 let _ = waker_state
25 .future
26 .borrow_mut()
27 .as_mut()
28 .poll(&mut Context::from_waker(&waker));
29}
30
31/// In a cleanup callback, this is set to `true` before calling `wake`, and `false` afterwards.
32/// This ensures that `wake` will not actually run the future, but instead cancel it and run its destructor.
33pub static CLEANUP: AtomicBool = AtomicBool::new(false);
34
35// This module contains the implementation of a waker we're using for waking
36// top-level futures (the ones returned by canister methods). Rc handles the
37// heap management for us. Hence, it will be deallocated once we exit the scope and
38// we're not interested in the result, as it can only be a unit `()` if the
39// waker was used as intended.
40// Sizable unsafe code is mandatory here; Future::poll cannot be executed without implementing
41// RawWaker in terms of raw pointers.
42mod waker {
43 use super::*;
44 use std::{
45 rc::Rc,
46 sync::atomic::Ordering,
47 task::{RawWaker, RawWakerVTable, Waker},
48 };
49
50 // The fields have separate RefCells in order to be modified separately.
51 pub(crate) struct WakerState {
52 pub future: RefCell<Pin<Box<dyn Future<Output = ()>>>>,
53 pub previous_trap: Cell<bool>,
54 }
55
56 static MY_VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
57
58 /// # Safety
59 ///
60 /// The pointer must be an owning (i.e. represented in the refcount), Rc-allocated pointer to a `WakerState`.
61 unsafe fn raw_waker(ptr: *const ()) -> RawWaker {
62 // SAFETY: All the function pointers in MY_VTABLE correctly operate on the pointer in question.
63 RawWaker::new(ptr, &MY_VTABLE)
64 }
65
66 /// # Safety
67 ///
68 /// This function should only be called by a [Waker] created by [`waker`].
69 unsafe fn clone(ptr: *const ()) -> RawWaker {
70 // SAFETY: The function's contract guarantees that this pointer is an Rc to a WakerState, and borrows the data from ptr.
71 unsafe {
72 Rc::increment_strong_count(ptr);
73 raw_waker(ptr)
74 }
75 }
76
77 // Our waker will be called if one of the response callbacks is triggered.
78 // Then, the waker will restore the future from the pointer we passed into the
79 // waker inside the `spawn` function and poll the future again. Rc takes care of
80 // the heap management for us. If CLEANUP is set, then we're recovering from
81 // a callback trap, and want to drop the future without executing any more of it;
82 // if previous_trap is set, then we already recovered from a callback trap in a
83 // different callback, and should immediately trap again in this one.
84 //
85 /// # Safety
86 ///
87 /// This function should only be called by a [Waker] created by [`waker`].
88 unsafe fn wake(ptr: *const ()) {
89 // SAFETY: The function's contract guarantees that the pointer is an Rc to a WakerState, and that this call takes ownership of the data.
90 let state = unsafe { Rc::from_raw(ptr as *const WakerState) };
91 // Must check CLEANUP *before* previous_trap, as we may be recovering from the following immediate trap.
92 if super::CLEANUP.load(Ordering::Relaxed) {
93 state.previous_trap.set(true);
94 } else if state.previous_trap.get() {
95 panic!("Call already trapped");
96 } else {
97 let waker = waker(Rc::clone(&state));
98 let Ok(mut borrow) = state.future.try_borrow_mut() else {
99 // If this is already borrowed, then wake was called from inside poll. There's not a lot we can do about this - we are not
100 // a true scheduler and so cannot immediately schedule another poll, nor can we reentrantly lock the future. So we ignore it.
101 // This will be disappointing to types like FuturesUnordered that expected this to work, but since the only source of asynchrony
102 // and thus a guaranteed source of wakeup notifications is the ic0.call_new callback, this shouldn't cause any actual problems.
103 return;
104 };
105 let pinned_future = borrow.as_mut();
106 let _ = pinned_future.poll(&mut Context::from_waker(&waker));
107 }
108 }
109
110 /// # Safety
111 ///
112 /// This function should only be called by a [Waker] created by [waker].
113 unsafe fn wake_by_ref(ptr: *const ()) {
114 // SAFETY:
115 // The function's contract guarantees that the pointer is an Rc to a WakerState, and that this call borrows the data.
116 // wake has the same contract, except it takes ownership instead of borrowing. Which just requires incrementing the refcount.
117 unsafe {
118 Rc::increment_strong_count(ptr as *const WakerState);
119 wake(ptr);
120 }
121 }
122
123 /// # Safety
124 ///
125 /// This function should only be called by a [Waker] created by [waker].
126 unsafe fn drop(ptr: *const ()) {
127 // SAFETY: The function contract guarantees that the pointer is an Rc to a WakerState, and that this call takes ownership of the data.
128 unsafe {
129 Rc::from_raw(ptr as *const WakerState);
130 }
131 }
132
133 /// Creates a new Waker.
134 pub(crate) fn waker(state: Rc<WakerState>) -> Waker {
135 let ptr = Rc::into_raw(state);
136 // SAFETY:
137 // The pointer is an owning, Rc-allocated pointer to a WakerState, and therefore can be passed to raw_waker
138 // The functions in the vtable are passed said ptr
139 // The functions in the vtable uphold RawWaker's contract
140 unsafe { Waker::from_raw(raw_waker(ptr as *const ())) }
141 }
142}