ic_cdk/
futures.rs

1//! Functions relating to the async executor.
2//!
3//! ## Running async tasks
4//!
5//! Most async tasks can be run just by changing your canister entry point to `async`:
6//!
7//! ```
8//! # use ic_cdk::update;
9//! # async fn some_other_async_fn() {}
10//! #[update]
11//! async fn foo() {
12//!     some_other_async_fn().await;
13//! }
14//! ```
15//!
16//! To run async tasks in the *background*, however, use [`spawn`]:
17//!
18//! ```
19//! # use ic_cdk::{update, futures::spawn};
20//! # async fn some_other_async_fn() {}
21//! #[update]
22//! async fn foo() {
23//!     spawn(async { some_other_async_fn().await; });
24//!     // do other stuff
25//! }
26//! ```
27//!
28//! The spawned future will not be run at the same time as the remaining code, nor will it run immediately. It will start
29//! running while `foo` awaits (or after it ends if it does not await). Unlike some other libraries, `spawn` does not
30//! return a join-handle; if you want to await multiple results concurrently, use `futures`' [`join_all`] function.
31//!
32//! ## Method lifetime
33//!
34//! The default [`spawn`] function will ensure a task does not outlive the canister method it was spawned in. If
35//! the method ends, and the task has `await`s that are not completed yet, it will trap. The method's lifetime lasts until
36//! it stops making inter-canister calls. What this means is that any await in a task created with `spawn` should be,
37//! or be driven by, an inter-canister call. If you instead await something dependent on a
38//! different canister method, or a timer, or similar, it is likely to trap. (This is unlikely to impact you if you
39//! don't use any 'remote' futures like channels or signals.)
40//!
41//! Where a task spawned with [`spawn`] will panic if it outlives the canister method, [`spawn_weak`] will simply
42//! cancel the task in such a case, dropping it.
43//!
44//! Note: for purposes of the executor, each invocation of a repeated [timer] is considered a separate canister method.
45//!
46//! ## `spawn_migratory`
47//!
48//! The [`spawn_migratory`] function is a little different. Migratory tasks can outlive the canister method they were
49//! spawned in, and will migrate between different canister methods as needed; when awoken, they will resume in whatever
50//! context they were awoken in, instead of the context they were originally spawned in. Because they can move around,
51//! any functions referencing the current method (i.e. `msg_*`) are unreliable and should not be used from these tasks.
52//!
53//! "Background" is a tricky subject on the IC. Migratory tasks can only run in the context of a canister message.
54//! It takes from that call's instruction limit, which can introduce hidden sources of instruction limit based traps;
55//! if that call runs multiple concurrent tasks, state changes made by the migratory task may be observable in between them.
56//!
57//! Most importantly, a migratory task must never trap. When it traps, it will cancel (see below) the execution of the call
58//! whose context it's in, even though that call didn't do anything wrong, and it may not undo whatever caused it to trap,
59//! meaning the canister could end up bricked.
60//!
61//! ## Automatic cancellation
62//!
63//! Asynchronous tasks can be *canceled*, meaning that a partially completed function will halt at an
64//! `await` point, never complete, and drop its local variables as though it had returned. Cancellation
65//! (not counting [`spawn_weak`]) is caused by panics and traps: if an async function panics, time will be rewound to the
66//! previous await as though the code since then never ran, and then the task will be canceled.
67//!
68//! When a protected task traps, *all* protected tasks in the method will be canceled, as well as any pending migratory tasks.
69//! The system cannot know exactly which task panicked, so a conservatively large 'blast radius' is assumed.
70//!
71//! Use panics sparingly in async functions after the first await, and beware system functions that trap
72//! (which is most of them in the right context). Make atomic transactions between awaits wherever
73//! possible, and use [`scopeguard`] or a [`Drop`] impl for any cleanup functions that must run no matter what.
74//! If an await cannot be removed from the middle of a transaction, and it must be rolled back if it fails,
75//! [`is_recovering_from_trap`] can be used to detect when the task is being automatically canceled.
76//!
77//! [`scopeguard`]: https://docs.rs/scopeguard
78//! [`join_all`]: https://docs.rs/futures/latest/futures/future/fn.join_all.html
79//! [timer]: https://docs.rs/ic-cdk-timers
80//! [`caller`]: crate::api::caller
81//! [`in_replicated_execution`]: crate::api::in_replicated_execution
82//! [`canister_self`]: crate::api::canister_self
83
84use std::{
85    future::Future,
86    pin::Pin,
87    sync::{
88        Arc,
89        atomic::{AtomicBool, Ordering},
90    },
91    task::{Context, Poll, Wake, Waker},
92};
93
94pub mod internals;
95
96/// Spawn a protected asynchronous task to run during the current canister method.
97///
98/// The task will panic if it outlives the canister method. To cancel it instead, use [`spawn_weak`].
99pub fn spawn<F: 'static + Future<Output = ()>>(future: F) {
100    pin_project_lite::pin_project! {
101        struct ProtectedTask<F> {
102            #[pin]
103            future: F,
104            completed: bool,
105        }
106        impl<F> PinnedDrop for ProtectedTask<F> {
107            #[track_caller]
108            fn drop(this: Pin<&mut Self>) {
109                if !this.completed && !ic_cdk_executor::is_recovering_from_trap() {
110                    panic!("protected task outlived its canister method (did you mean to use spawn_weak or spawn_migratory?)")
111                }
112            }
113        }
114    }
115    impl<F> Future for ProtectedTask<F>
116    where
117        F: Future<Output = ()>,
118    {
119        type Output = ();
120
121        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
122            let this = self.project();
123            let res = this.future.poll(cx);
124            if res.is_ready() {
125                *this.completed = true;
126            }
127            res
128        }
129    }
130    ic_cdk_executor::spawn_protected(ProtectedTask {
131        future,
132        completed: false,
133    });
134}
135
136/// Spawn a weak asynchronous task to run during the current canister method.
137///
138/// If the task outlives the canister method, it will be dropped.
139pub fn spawn_weak<F: 'static + Future<Output = ()>>(future: F) {
140    ic_cdk_executor::spawn_protected(future);
141}
142
143/// Spawn an asynchronous task that can outlive the current canister method.
144pub fn spawn_migratory<F: 'static + Future<Output = ()>>(future: F) {
145    ic_cdk_executor::spawn_migratory(future);
146}
147
148/// Tells you whether the current async fn is being canceled due to a trap/panic.
149///
150/// In a destructor, `is_recovering_from_trap` serves the same purpose as
151/// [`std::thread::panicking`] - it tells you whether the destructor is executing *because* of a trap,
152/// as opposed to just because the scope was exited, so you could e.g. implement mutex poisoning.
153///
154/// For information about when and how this occurs, see [the module docs](self).
155pub fn is_recovering_from_trap() -> bool {
156    ic_cdk_executor::is_recovering_from_trap()
157}
158
159/// Like `spawn`, but preserves the code ordering behavior of `ic-cdk` 0.17 and before.
160///
161/// Namely, the spawned future will start executing immediately, with control returning to the surrounding code
162/// after the first `await`.
163pub fn spawn_017_compat<F: 'static + Future<Output = ()>>(fut: F) {
164    struct DummyWaker(AtomicBool);
165    impl Wake for DummyWaker {
166        fn wake(self: Arc<Self>) {
167            self.0.store(true, Ordering::SeqCst);
168        }
169    }
170    // Emulated behavior: A spawned future is polled once immediately, then backgrounded and run at a normal pace.
171    // We poll it once with an unimplemented waker, then spawn it, which will poll it again with the real waker.
172    // In a correctly implemented future, this second poll should overwrite the fake waker with the real one.
173    // If the `poll` function calls `wake`, call it again until it is 'really' pending.
174    let mut pin = Box::pin(fut);
175    loop {
176        let dummy = Arc::new(DummyWaker(AtomicBool::new(false)));
177        let poll = pin
178            .as_mut()
179            .poll(&mut Context::from_waker(&Waker::from(dummy.clone())));
180        match poll {
181            Poll::Ready(()) => break,
182            Poll::Pending => {
183                if dummy.0.load(Ordering::SeqCst) {
184                    continue;
185                } else {
186                    spawn(pin);
187                    break;
188                }
189            }
190        }
191    }
192}