Async Best Practices
Rules for responsive async Rust services - non-blocking I/O, structured concurrency, and honest runtime boundaries.
Search across all documentation pages
Rules for responsive async Rust services - non-blocking I/O, structured concurrency, and honest runtime boundaries.
tokio-console when latency regresses.async fn; binaries own #[tokio::main]. One runtime per process.spawn_blocking as default.worker_threads after measurement. More threads is not free scalability.current_thread only for tests or !Send + LocalSet. Servers use multi_thread.std::thread::sleep or sync I/O on worker threads. Use tokio::time::sleep and tokio::fs.spawn_blocking. Cap concurrency with semaphores.std::sync::MutexGuard across .await. Use tokio::sync::Mutex or short scopes.par_iter in spawn_blocking. Do not block workers on parallel sections.join! for independent awaits on one task. spawn when lifetimes or CPU isolation need it.select! loops with cancel-safe branches. Document which methods are cancel-safe.CancellationToken or watch. Do not rely on process kill alone.JoinHandle. No leaked background tasks after errors.Arc + cheap clones. DB pool, HTTP client, config snapshots.Arc<Mutex<App>>. Actors for complex mutable state.Send on multi-thread runtime. No Rc across await in spawned work.async-trait or native async traits intentionally. Know dyn vs generic trade-offs.Result. Map to stable HTTP/problem responses.tracing spans per request and task. Include trace ids across await.#[tokio::test] and time::pause. Deterministic timeout tests.No - only when many concurrent I/O waits share few threads. CPU-bound or low concurrency may favor sync threads.
Blocking worker threads with sync I/O or CPU loops - causes tail latency spikes under load.
Arc<PgPool> in state, async handlers, timeouts on queries, migrations offline.
join! for coordinated parallel awaits; spawn for fire-and-forget or long-lived workers with JoinHandle tracking.
Dropped handler future cancels work - make side effects idempotent or move critical commits outside cancellable section.
Valid for embedded or !Send futures - not typical for multi-core API servers.
thiserror in libraries, anyhow in binaries; never swallow errors in spawned tasks.
Bounded channels, semaphores on outbound calls, HTTP 503 when overloaded - unbounded queues hide failure until OOM.
Unit test pure logic sync; integration test handlers with reqwest against axum::Router in #[tokio::test].
Identify top 3 blocking calls in hot path; replace with async or spawn_blocking with limit.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Reviewed by Chris St. John·Last updated Jul 16, 2026