tokio-performance
Skill15 starsChanged 5 months agoMITView on GitHub
Audit and design Tokio async Rust systems for throughput, latency, fairness, contention, blocking, and bounded concurrency. Use for Tokio runtime design, async performance reviews, task scheduling, spawn/spawn_blocking decisions, locks, channels, I/O, and tail-latency problems.
In this repository29 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- llms-full.txtllms.txt
- llms.txtllms.txt
- anti-ai-slopSkill
- architecture-diagramSkill
- atomic-commitSkill
- build-rustSkill
- codacySkill
- crates-io-name-checkSkill
- dora-reportSkill
- goap-agentSkill
- harnessSkill
- issue-triageSkill
- lint-rustSkill
- metrics-reporterSkill
- privacy-firstSkill
- release-rustSkill
- reviewing-pull-requestsSkill
- secret-lintSkill
- self-fix-loopSkill
- skill-creatorSkill
- skill-evaluatorSkill
- stacked-prsSkill
- task-decompositionSkill
- test-rustSkill
- triz-analysisSkill
- triz-solverSkill
- verify-actionsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
15 rules. About 0 are common, 3 are distinctive, and 12 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 49
Cooperative Yielding: Use yield_now() only when measurements or loop structure demonstrate worker thread starvation in repeatedly-ready …
- Very specific: line 57
Keep lock scopes minimal and never hold locks across unrelated .await calls.
- Very specific: line 64
tokio::task::yield_now(): Use yield_now() deliberately only when measurements or tight loop structure demonstrate starvation. - Very specific: line 65
Batching: Use batching (e.g., buffer_unordered, batch channel receives) when it reduces scheduling, syscall, or task coordination overhead.
- Distinctive: line 69
Always default to bounded designs for network, filesystem, database, external API calls, or worker queues:
- Very specific: line 70
Bounded Channels: Prefer tokio::sync::mpsc::channel(capacity) over unbounded channels to enforce upstream backpressure.
- Very specific: line 71
Bounded Fan-out: Use tokio::sync::Semaphore, futures::stream::StreamExt::buffer_unordered, or fixed worker pools rather than unbounded …
- Distinctive: line 76
Prefer synchronization mechanisms in this order:
- Very specific: line 78
Immutable Snapshots: Use Arc<T>, ArcSwap, or Copy-On-Write (std::sync::RwLock<Arc<T>>) for read-heavy shared state or registries.
- Very specific: line 79
Short Synchronous Locks: Use std::sync::Mutex or parking_lot::Mutex for extremely short data-only updates (e.g., updating a counter or …
- Very specific: line 80
Async Locks: Use tokio::sync::Mutex or tokio::sync::RwLock ONLY when access across .await points is genuinely required or when waiting for …
- Very specific: line 118
Deterministic rows are gates, performance rows are evidence: No CI check compares absolute timings across machines.
- Distinctive: line 127
Add a workload instead of arguing: If a trade-off is not represented, extend the harness first, then cite the new row in the discussion.
- Very specific: line 168
triz-analysis: Use .agents/skills/triz-analysis/SKILL.md for evaluating throughput vs. - Very specific: line 169
anti-ai-slop: Use after performance refactors to keep code simple and readable.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Tokio Performance
Performance guidance for production Rust systems using Tokio. Optimize measured behavior from workload characteristics, not async code by reflex.
Core Rule
Measure first. Derive execution models, concurrency bounds, and lock strategies from workload characteristics.
Do not assume:
asyncis faster than synchronous codespawn_blockingis always better for CPU worktokio::sync::MutexorRwLockis preferable to a synchronous lock or atomic/snapshot pattern- more tasks or more runtimes mean more throughput
yield_now()everywhere improves fairness
Workload Decision Matrix
| Workload Type | Characteristics | Recommended Execution Model | Key Considerations |
| Short Bounded CPU | Fast, non-blocking calculations (< 10–100 µs), memory parsing, small serializations | Direct Execution on Tokio Worker | Offloading to spawn_blocking introduces thread context switches and queue overhead that exceed the work duration. |
| Genuinely Blocking | Synchronous disk I/O, blocking C/FFI calls, legacy blocking SDKs, blocking DB drivers | spawn_blocking or Dedicated Thread Pool | Prevents blocking the Tokio worker thread event loop. |
| Long or Sustained CPU | Image processing, heavy cryptography, compression, large payload parsing (> 100 µs - ms+) | spawn_blocking or Dedicated Worker Pool | Protects Tokio worker threads from starvation. For sustained heavy CPU, prefer a dedicated Rayon/thread pool to avoid exhausting Tokio's blocking pool. |
| Batchable Operations | High-rate small I/O operations, frequent channel sends, small database updates | Batching & Micro-buffering | Consolidates syscalls, locks, and channel synchronization overhead. |
| Isolated Subsystems | Multi-tenant latency isolation, distinct thread priority requirements | Workload Isolation | Isolate via bounded semaphores or separate thread pools before considering a separate Tokio runtime. |
Correctness vs. Performance Heuristics
Distinguish non-negotiable correctness rules from workload-driven performance heuristics:
Correctness Constraints (Mandatory)
- Never hold lock guards across
.awaitpoints or during I/O: Holding a std/tokio lock guard across.awaitleads to deadlocks, starvation, or sending non-Sendguard types across thread boundaries. - Never perform blocking synchronous syscalls directly on Tokio worker threads: Blocking a worker thread starves all other tasks multiplexed on that worker thread.
Performance Heuristics (Workload-Driven)
spawn_blockingvs. Direct Execution: Balance context switch/queue overhead against worker thread yield time (~10–100 µs threshold).- Lock Type Selection:
std::sync::Mutex(short data-only operations) vs.tokio::sync::Mutex(critical sections that MUST hold state across.awaitor await turn access). - Cooperative Yielding: Use
yield_now()only when measurements or loop structure demonstrate worker thread starvation in repeatedly-ready loops.
Decision Protocol
- Identify the workload: I/O-bound, short CPU-bound, long CPU-bound, blocking sync API, bursty, or latency-sensitive.
- Define the target: throughput, median latency, tail latency (p95/p99), fairness, or resource limits.
- Distinguish correctness constraints from performance heuristics.
- Bound fan-out and queue growth by default when downstream capacity is finite.
- Keep lock scopes minimal and never hold locks across unrelated
.awaitcalls. - Measure under representative load before and after changes.
Scheduling & Fairness
- Avoid long synchronous sections inside async tasks.
- Repeatedly-ready worker loops: Tasks that compute or poll continuously without hitting an I/O yield point can starve other tasks assigned to the same worker thread. Evaluate for scheduler starvation and tail-latency impact.
tokio::task::yield_now(): Useyield_now()deliberately only when measurements or tight loop structure demonstrate starvation. Do not insert it mechanically. Prefer Tokio's automatic cooperative budget (tokio::task::consume_budget) or event-driven channel backpressure.- Batching: Use batching (e.g.,
buffer_unordered, batch channel receives) when it reduces scheduling, syscall, or task coordination overhead.
Bounded Concurrency & Backpressure
Always default to bounded designs for network, filesystem, database, external API calls, or worker queues:
- Bounded Channels: Prefer
tokio::sync::mpsc::channel(capacity)over unbounded channels to enforce upstream backpressure. - Bounded Fan-out: Use
tokio::sync::Semaphore,futures::stream::StreamExt::buffer_unordered, or fixed worker pools rather than unboundedtokio::spawnloops. - Timeouts & Shedding: Combine queue capacity limits with
tokio::time::timeoutand explicit load-shedding to prevent cascading failures under heavy load.
Synchronization & Lock Lifetimes
Prefer synchronization mechanisms in this order:
- Ownership and Message Passing: Move data via bounded channels (
mpsc,oneshot). - Immutable Snapshots: Use
Arc<T>,ArcSwap, or Copy-On-Write (std::sync::RwLock<Arc<T>>) for read-heavy shared state or registries. Lookups clone anArcpointer (~1-2 ns) and release the lock immediately before lookup and async execution. On mutation, create a cloned snapshot under a short write lock and swap theArc. - Short Synchronous Locks: Use
std::sync::Mutexorparking_lot::Mutexfor extremely short data-only updates (e.g., updating a counter or inserting into a HashMap). Release the lock immediately before any.await. - Async Locks: Use
tokio::sync::Mutexortokio::sync::RwLockONLY when access across.awaitpoints is genuinely required or when waiting for lock acquisition needs to be async non-blocking.
Pattern for state lookup and async execution:
lock -> lookup / clone snapshot -> drop lock -> await external workAnti-Patterns & Counter-Examples
The five costliest Tokio misapplications — spawn_blocking and yield_now used
reflexively, a second runtime, RwLock where Mutex or a snapshot fits, and a lock
held across .await — are worked through with symptom, cost, and the correct
alternative in references/counter-examples.md. Read it before accepting any of them.
Regression Harness (Preferred Evidence)
Performance claims in this repository are backed by the Tokio runtime harness. Reach for it
before proposing an execution-model change, a yield_now(), a lock swap, or a second runtime:
| Harness | What it answers | Nature |
benchmarks/benches/tokio_runtime_bench.rs, benchmarks/benches/tokio_runtime_pressure_bench.rs | execution strategy (direct vs spawn_blocking), cooperative-yield cost, bounded vs unbounded fan-out, registry contention, service-queue latency | informational telemetry, reports p50/p95/p99/max |
benchmarks/tests/tokio_runtime_behavior.rs | bounded-queue backpressure, repeatedly-ready starvation and its yield cure, spawn_blocking thread identity and deadlock-freedom, permit bounds, registry no-lost-update | deterministic pass/fail, no timing thresholds |
cargo bench -p benchmarks --bench tokio_runtime --bench tokio_runtime_pressure -- --quick # full run: omit --quick
cargo nextest run -p benchmarks # deterministic invariantsEach benchmark group runs an instrumented sampling pass whose percentiles are printed in
bencher format, so scripts/parse_bench.py records them as …/p50, …/p95, …/p99, …/max
rows in benchmarks/events/** — the same machine-readable event stream as every other bench.
How to Interpret Results
- Read tails, not averages. A change is only interesting when
p95/p99move outside run-to-run noise. Criterion's slope covers medians; the emitted percentiles cover tails. - Deterministic rows are gates, performance rows are evidence. No CI check compares
absolute timings across machines;
benchmarks/events/**is a time series for trend comparison, never a threshold. - Compare like with like. Same machine class, toolchain, and workload size. A laptop number does not transfer to production; a GitHub-hosted runner number does not either.
- Change architecture only for the symptom you measured.
p99of direct CPU units that degrades as fan-out grows justifiesspawn_blocking; a starved peer in the behavior test justifiesyield_now()or batching at that specific loop; queue-depth growth justifies a bounded channel. A second runtime (Counter-Example 4) needs all of the above to be ruled out. - Add a workload instead of arguing. If a trade-off is not represented, extend the harness first, then cite the new row in the discussion.
Observability
Capture metrics to distinguish execution phase behavior:
- task scheduling delay
- task poll duration
- queue depth and channel capacity
spawn_blockingpool usage- lock wait time and contention
- I/O latency vs application work time
Review Checklist
- Workload type (short CPU, blocking sync, long CPU, batchable, isolated) is explicitly identified.
- Execution model (
direct,spawn_blocking, dedicated pool) is chosen based on workload characteristics. - Correctness constraints (lock across await, blocking on worker thread) are respected.
- No unbounded task fan-out (
Semaphore,buffer_unordered, or bounded channel used). - Channels have deliberate capacity limits and backpressure.
- Locks are data-only, short-lived, and never held across
.awaitcalls or external I/O. - Repeatedly-ready loops are evaluated for fairness and scheduler starvation without cargo-culting
yield_now(). - Claims of async performance improvements are backed by the regression harness (
benchmarks/benches/tokio_runtime_bench.rstails orbenchmarks/tests/tokio_runtime_behavior.rsinvariants), not by intuition.
Rationalizations
| Rationalization | Reality |
| "Moving this small parse to spawn_blocking will make it faster." | Thread context switching and queue overhead outweigh short CPU operations (<10-50µs). Run directly. |
| "Spawning an unbounded number of tasks is fine because Tokio is lightweight." | Unbounded task creation exhausts file descriptors, network buffers, and memory. Use semaphores or bounded channels. |
| "A yield_now() loop fixes my starvation problem." | Loops relying on yield_now() to prevent starvation indicate design flaws. Use event-driven notifications or channels. |
Red Flags
- Holding
std::sync::Mutexlocks across.awaitpoints - Performing blocking synchronous filesystem or network I/O directly on Tokio worker threads
- Unbounded
tokio::spawnloops without backpressure or permits
Integration & Cross-References
AGENTS.md: Canonical project contract for coding agents.agents-docs/conventions.md: Project-wide coding conventions and invariants.triz-analysis: Use.agents/skills/triz-analysis/SKILL.mdfor evaluating throughput vs. latency, contention, and async vs. blocking trade-offs using TRIZ inventive principles.anti-ai-slop: Use after performance refactors to keep code simple and readable.
References
- Tokio documentation: https://tokio.rs/tokio/tutorial
- Principles for Fast Tokio Applications: https://dial9-rs.github.io/blog/principles-for-fast-tokio-applications/
- Rust Performance Book: https://nnethercote.github.io/perf-book/
- Harness entry point:
benchmarks/README.md
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

