Tokio
Tokio
Tokio is the runtime behind async Rust in Rust. The part worth remembering is not "async equals fast." The real split is:
tokio::join!gives concurrency inside one parent task — see Concurrent Is Not Paralleltokio::spawngives the runtime an independent task it can schedule across worker threadstokio::task::spawn_blockingkeeps synchronous or CPU-heavy work off the async worker pool
This page is the hub. The load-bearing rules each have their own atomic note.
Rules I want to keep
- Size throughput by worker threads. Do not treat
#[tokio::main]as a bonus worker for parallelism math. - Classify the work first — async I/O, short CPU, bounded blocking, long-lived blocking, or heavy CPU parallelism. See Classify Async Work Before Refactoring.
- Concurrent is not parallel. Use
join!for a small fixed set of async I/O operations; usespawnwhen work should become its own task and may benefit from multi-thread scheduling. See Concurrent Is Not Parallel. - Bound the fan-out. Default to
JoinSetplus aSemaphore, not unbounded spawn storms. See Bounded Concurrency First. - Never hold a mutex guard across
.await. See Don't Hold a Lock Across Await. - Prefer
std::sync::Mutexorparking_lot::Mutexfor short sync critical sections. - Use
tokio::sync::Mutexonly when the guarded operation truly has to cross.await. - Use
spawn_blockingfor bounded sync or CPU-heavy work. - Use a dedicated
std::thread::spawnworker for long-lived blocking loops.
What this means for mxr
Good places to parallelize:
- per-account sync work
- attachment extraction
- semantic prep work that does not immediately fight over the same lock
- expensive local decode or rendering steps
Bad places to parallelize:
- tiny operations where spawn overhead dominates
- paths that still serialize on one lock or one writer
- anything that holds
search,semantic, or similar shared state longer than necessary
Pointer
Repo reference guide: docs/reference/tokio-runtime-guide.md