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//! "Background" is a tricky subject on the IC. Background tasks can only run in the context of a canister message.
33//! If you await a future whose completion you manually trigger in code, such as sending to an async channel,
34//! then the code after the await will be in the call context of whatever you completed it in. This means that global state
35//! like [`caller`], [`in_replicated_execution`], and even [`canister_self`] may have changed. (The canister method
36//! itself cannot await anything triggered by another canister method, or you will get an error that it 'failed to reply'.)
37//! It will also take from that call's instruction limit, which can introduce hidden sources of instruction limit based traps.
38//!
39//! Most importantly, a background task that runs in other call contexts must never trap. When it traps, it will cancel
40//! (see below) the execution of the call whose context it's in, even though that call didn't do anything wrong, and it
41//! may not undo whatever caused it to trap, meaning the canister could end up bricked. Tasks that you expect to complete
42//! before the canister method ends are safe, but traps/panics in tasks that are expected to continue running into other
43//! calls/timers may produce surprising results and behavioral errors.
44//!
45//! ## Automatic cancellation
46//!
47//! Asynchronous tasks can be *canceled*, meaning that a partially completed function will halt at an
48//! `await` point, never complete, and drop its local variables as though it had returned. Cancellation
49//! is caused by panics and traps: if an async function panics, time will be rewound to the
50//! previous await as though the code since then never ran, and then the task will be canceled.
51//!
52//! Use panics sparingly in async functions after the first await, and beware system functions that trap
53//! (which is most of them in the right context). Make atomic transactions between awaits wherever
54//! possible, and use [`scopeguard`] or a [`Drop`] impl for any cleanup functions that must run no matter what.
55//! If an await cannot be removed from the middle of a transaction, and it must be rolled back if it fails,
56//! [`is_recovering_from_trap`] can be used to detect when the task is being automatically canceled.
57//!
58//! [`scopeguard`]: https://docs.rs/scopeguard
59//! [`join_all`]: https://docs.rs/futures/latest/futures/future/fn.join_all.html
60//! [`caller`]: crate::api::caller
61//! [`in_replicated_execution`]: crate::api::in_replicated_execution
62//! [`canister_self`]: crate::api::canister_self
63
64use std::future::Future;
65
66/// Spawn an asynchronous task to run in the background. For information about semantics, see
67/// [the module docs](self).
68pub fn spawn<F: 'static + Future<Output = ()>>(future: F) {
69    ic_cdk_executor::spawn(future);
70}
71
72/// Execute an update function in a context that allows calling [`spawn`].
73///
74/// You do not need to worry about this function unless you are avoiding the attribute macros.
75///
76/// Background tasks will be polled in the process (and will not be run otherwise).
77/// Panics if called inside an existing executor context.
78pub fn in_executor_context<R>(f: impl FnOnce() -> R) -> R {
79    crate::setup();
80    ic_cdk_executor::in_executor_context(f)
81}
82
83/// Execute a composite query function in a context that allows calling [`spawn`].
84///
85/// You do not need to worry about this function unless you are avoiding the attribute macros.
86///
87/// Background composite query tasks will be polled in the process (and will not be run otherwise).
88/// Panics if called inside an existing executor context.
89pub fn in_query_executor_context<R>(f: impl FnOnce() -> R) -> R {
90    crate::setup();
91    ic_cdk_executor::in_query_executor_context(f)
92}
93
94/// Tells you whether the current async fn is being canceled due to a trap/panic.
95///
96/// In a destructor, `is_recovering_from_trap` serves the same purpose as
97/// [std::thread::panicking] - it tells you whether the destructor is executing *because* of a trap,
98/// as opposed to just because the scope was exited, so you could e.g. implement mutex poisoning.
99///
100/// For information about when and how this occurs, see [the module docs](self).
101pub fn is_recovering_from_trap() -> bool {
102    ic_cdk_executor::is_recovering_from_trap()
103}