Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cancellation, Shutdown, and Blocking Work

Rule

Use cooperative cancellation, join owned tasks, put timeouts at operation boundaries, and isolate blocking or CPU-heavy work from Tokio workers.

Why

Async work may be dropped at any .await; explicit teardown and isolation keep state recoverable and the runtime responsive.

Activation

Apply this page to long-lived loops, graceful shutdown, timeouts, cancellation safety, blocking I/O, CPU-heavy work, or teardown.

Do

  • Pass an explicit cancellation signal into long-lived tasks.
  • Race service-loop work with shutdown using select!.
  • Join task owners during teardown and surface failures.
  • Put timeouts around external calls, jobs, subprocesses, and shutdown phases.
  • Keep inner helpers timeout-free unless they own an operation boundary.
  • Make interrupted sections idempotent, restartable, or backed by resumable state.
  • Use spawn_blocking for bounded blocking or short CPU-heavy work.
  • Use a dedicated pool, queue, or Rayon for sustained CPU work.
  • Drop locks before awaiting, blocking, callbacks, or expensive computation.

Avoid

  • Do not rely on dropping a future as important-work shutdown.
  • Do not use abort as the normal path when cleanup matters.
  • Do not put non-cancel-safe work in select! without recoverable state.
  • Do not scatter timeouts around small helpers.
  • Do not run blocking I/O or long CPU work on Tokio workers.
  • Do not expect a started spawn_blocking closure to be cancelled.

Example

#![allow(unused)]
fn main() {
pub async fn run_worker(
    mut jobs: mpsc::Receiver<Job>,
    shutdown: CancellationToken,
) -> Result<(), WorkerError> {
    loop {
        let job = tokio::select! {
            () = shutdown.cancelled() => return Ok(()),
            job = jobs.recv() => job.ok_or(WorkerError::QueueClosed)?,
        };

        timeout(Duration::from_secs(10), process_job(job))
            .await
            .map_err(|_| WorkerError::TimedOut)??;
    }
}
}

Exceptions

  • Abort best-effort tasks that own no cleanup-sensitive state.
  • Let short request tasks complete when request drop already owns cancellation.
  • Keep known tiny bounded CPU work on Tokio.