Module futures

Source
Expand description

Functions relating to the async executor.

§Running async tasks

Most async tasks can be run just by changing your canister entry point to async:

#[update]
async fn foo() {
    some_other_async_fn().await;
}

To run async tasks in the background, however, use spawn:

#[update]
async fn foo() {
    spawn(async { some_other_async_fn().await; });
    // do other stuff
}

The spawned future will not be run at the same time as the remaining code, nor will it run immediately. It will start running while foo awaits (or after it ends if it does not await). Unlike some other libraries, spawn does not return a join-handle; if you want to await multiple results concurrently, use futuresjoin_all function.

“Background” is a tricky subject on the IC. Background tasks can only run in the context of a canister message. If you await a future whose completion you manually trigger in code, such as sending to an async channel, then the code after the await will be in the call context of whatever you completed it in. This means that global state like caller, in_replicated_execution, and even canister_self may have changed. (The canister method itself cannot await anything triggered by another canister method, or you will get an error that it ‘failed to reply’.) It will also take from that call’s instruction limit, which can introduce hidden sources of instruction limit based traps.

Most importantly, a background task that runs in other call contexts must never trap. When it traps, it will cancel (see below) the execution of the call whose context it’s in, even though that call didn’t do anything wrong, and it may not undo whatever caused it to trap, meaning the canister could end up bricked. Tasks that you expect to complete before the canister method ends are safe, but traps/panics in tasks that are expected to continue running into other calls/timers may produce surprising results and behavioral errors.

§Automatic cancellation

Asynchronous tasks can be canceled, meaning that a partially completed function will halt at an await point, never complete, and drop its local variables as though it had returned. Cancellation is caused by panics and traps: if an async function panics, time will be rewound to the previous await as though the code since then never ran, and then the task will be canceled.

Use panics sparingly in async functions after the first await, and beware system functions that trap (which is most of them in the right context). Make atomic transactions between awaits wherever possible, and use scopeguard or a Drop impl for any cleanup functions that must run no matter what. If an await cannot be removed from the middle of a transaction, and it must be rolled back if it fails, is_recovering_from_trap can be used to detect when the task is being automatically canceled.

Functions§

in_executor_context
Execute an update function in a context that allows calling spawn.
in_query_executor_context
Execute a composite query function in a context that allows calling spawn.
is_recovering_from_trap
Tells you whether the current async fn is being canceled due to a trap/panic.
spawn
Spawn an asynchronous task to run in the background. For information about semantics, see the module docs.