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}