Tokio Async Code Review
Review Workflow
- 1. Check Cargo.toml — Note tokio feature flags (
full, rt-multi-thread, macros, sync, etc.). Missing features cause confusing compile errors. - Check runtime setup — Is
#[tokio::main] or manual runtime construction used? Multi-thread vs current-thread? - Scan for blocking — Search for
std::fs, std::net, std::thread::sleep, CPU-heavy loops in async functions. - Check channel usage — Match channel type to communication pattern (mpsc, broadcast, oneshot, watch).
- Check sync primitives — Verify correct mutex type, proper guard lifetimes, no deadlock potential.
Output Format
Report findings as:
CODEBLOCK0
Quick Reference
references/sync-primitives.md |
| mpsc, broadcast, oneshot, watch channel patterns |
references/channels.md |
| Pin, cancellation, Future internals, select!, blocking bridge |
references/pinning-cancellation.md |
Review Checklist
Runtime Configuration
- - [ ] Tokio features in Cargo.toml match actual usage
- [ ] Runtime flavor matches workload (
multi_thread for I/O-bound, current_thread for simpler cases) - [ ]
#[tokio::test] used for async tests (not manual runtime construction) - [ ] Worker thread count configured appropriately for production
Task Management
- - [ ]
spawn return values (JoinHandle) are tracked, not silently dropped - [ ]
spawn_blocking used for CPU-heavy or synchronous I/O operations - [ ] Tasks respect cancellation (via
CancellationToken, select!, or shutdown channels) - [ ]
JoinError (task panic or cancellation) is handled, not just unwrapped - [ ]
tokio::select! branches are cancellation-safe - [ ] Native
async fn in traits used instead of async-trait crate where possible (stable since Rust 1.75) - [ ] RPIT lifetime capture reviewed in async contexts —
-> impl Future now captures all in-scope lifetimes in edition 2024
Sync Primitives
- - [ ]
tokio::sync::Mutex used when lock is held across .await; std::sync::Mutex for short non-async sections - [ ] No mutex guard held across await points (deadlock risk)
- [ ]
Semaphore used for limiting concurrent operations (not ad-hoc counters) - [ ]
RwLock used when read-heavy workload (many readers, infrequent writes) - [ ]
Notify used for simple signaling (not channel overhead) - [ ]
std::sync::LazyLock used instead of once_cell::sync::Lazy or lazy_static! for runtime-initialized singletons (stable since Rust 1.80) - [ ]
if let lock guard patterns reviewed for edition 2024 temporary scoping — temporaries drop earlier, may change borrow validity
Channels
- - [ ] Channel type matches pattern: mpsc for back-pressure, broadcast for fan-out, oneshot for request-response, watch for latest-value
- [ ] Bounded channels have appropriate capacity (not too small = deadlock, not too large = memory)
- [ ]
SendError / RecvError handled (indicates other side dropped) - [ ] Broadcast
Lagged errors handled (receiver fell behind) - [ ] Channel senders dropped when done to signal completion to receivers
Timer and Sleep
- - [ ]
tokio::time::sleep used instead of INLINECODE35 - [ ]
tokio::time::timeout wraps operations that could hang - [ ]
tokio::time::interval used correctly (.tick().await for periodic work)
Severity Calibration
Critical
- - Blocking I/O (
std::fs::read, std::net::TcpStream) in async context without INLINECODE41 - Mutex guard held across
.await point (deadlock potential) - INLINECODE43 in async function (blocks runtime thread)
- Unbounded channel where back-pressure is needed (OOM risk)
Major
- -
JoinHandle silently dropped (lost errors, zombie tasks) - Missing
select! cancellation safety consideration - Wrong mutex type (std vs tokio) for the use case
- Missing timeout on network/external operations
Minor
- -
tokio::spawn for trivially small async blocks (overhead > benefit) - Overly large channel buffer without justification
- Manual runtime construction where
#[tokio::main] suffices - INLINECODE48 where contention is high enough to benefit from tokio's async mutex
Informational
- - Suggestions to use
tokio-util utilities (e.g., CancellationToken) - Tower middleware patterns for service composition
- Structured concurrency with INLINECODE51
- Migration from
async-trait crate to native async fn in traits - Migration from
once_cell / lazy_static to INLINECODE56 - Using
#[expect(lint)] instead of #[allow(lint)] for self-cleaning suppression
Valid Patterns (Do NOT Flag)
- -
std::sync::Mutex for short critical sections — tokio docs recommend this when no .await is inside the lock tokio::spawn without explicit join — Valid for background tasks with proper shutdown signaling- Unbuffered channel capacity of 1 — Valid for synchronization barriers
#[tokio::main(flavor = "current_thread")] in simple binaries — Not every app needs multi-thread runtimeclone() on Arc<T> before spawn — Required for moving into tasks, not unnecessary cloning- Large broadcast channel capacity — Valid when lagged errors are expensive (event sourcing)
- Native
async fn in traits without async-trait — Stable since 1.75; the crate is still valid for dyn dispatch cases + use<'a> on -> impl Future returns — Correct edition 2024 precise capture syntax to limit lifetime capture#[expect(clippy::type_complexity)] on complex async types — Self-cleaning alternative to #[allow], warns when suppression is no longer needed
Before Submitting Findings
Load and follow beagle-rust:review-verification-protocol before reporting any issue.
Tokio 异步代码审查
审查工作流程
- 1. 检查 Cargo.toml — 注意 tokio 特性标志(full、rt-multi-thread、macros、sync 等)。缺少特性会导致令人困惑的编译错误。
- 检查运行时设置 — 使用 #[tokio::main] 还是手动构建运行时?多线程还是当前线程?
- 扫描阻塞操作 — 在异步函数中搜索 std::fs、std::net、std::thread::sleep、CPU 密集型循环。
- 检查通道使用 — 根据通信模式匹配通道类型(mpsc、broadcast、oneshot、watch)。
- 检查同步原语 — 验证正确的互斥锁类型、适当的守卫生命周期、无死锁风险。
输出格式
按以下格式报告发现:
text
[文件:行号] 问题标题
严重级别:严重 | 主要 | 次要 | 信息性
问题描述及其重要性说明。
快速参考
references/sync-primitives.md |
| mpsc、broadcast、oneshot、watch 通道模式 |
references/channels.md |
| Pin、取消、Future 内部机制、select!、阻塞桥接 |
references/pinning-cancellation.md |
审查清单
运行时配置
- - [ ] Cargo.toml 中的 Tokio 特性与实际使用匹配
- [ ] 运行时风格与工作负载匹配(I/O 密集型用 multithread,简单情况用 currentthread)
- [ ] 异步测试使用 #[tokio::test](而非手动构建运行时)
- [ ] 工作线程数已针对生产环境适当配置
任务管理
- - [ ] spawn 返回值(JoinHandle)被跟踪,而非静默丢弃
- [ ] CPU 密集型或同步 I/O 操作使用 spawn_blocking
- [ ] 任务尊重取消(通过 CancellationToken、select! 或关闭通道)
- [ ] JoinError(任务恐慌或取消)已处理,而非仅 unwrap
- [ ] tokio::select! 分支是取消安全的
- [ ] 在可能的情况下使用 trait 中的原生 async fn 而非 async-trait crate(自 Rust 1.75 起稳定)
- [ ] 在异步上下文中审查了 RPIT 生命周期捕获 — 在 2024 版中,-> impl Future 现在捕获所有作用域内的生命周期
同步原语
- - [ ] 当锁在 .await 期间持有时使用 tokio::sync::Mutex;短的非异步代码段使用 std::sync::Mutex
- [ ] 没有在等待点持有互斥锁守卫(死锁风险)
- [ ] 使用 Semaphore 限制并发操作(而非临时计数器)
- [ ] 读密集型工作负载(多读少写)使用 RwLock
- [ ] 简单信号通知使用 Notify(而非通道开销)
- [ ] 运行时初始化的单例使用 std::sync::LazyLock 而非 oncecell::sync::Lazy 或 lazystatic!(自 Rust 1.80 起稳定)
- [ ] 审查了 2024 版临时作用域的 if let 锁守卫模式 — 临时变量提前释放,可能改变借用有效性
通道
- - [ ] 通道类型匹配模式:mpsc 用于背压,broadcast 用于扇出,oneshot 用于请求-响应,watch 用于最新值
- [ ] 有界通道具有适当的容量(太小 = 死锁,太大 = 内存问题)
- [ ] 处理了 SendError / RecvError(表示另一端已丢弃)
- [ ] 处理了广播 Lagged 错误(接收者落后)
- [ ] 通道发送者在完成后丢弃以向接收者发出完成信号
定时器和睡眠
- - [ ] 使用 tokio::time::sleep 而非 std::thread::sleep
- [ ] tokio::time::timeout 包装可能挂起的操作
- [ ] 正确使用 tokio::time::interval(.tick().await 用于周期性工作)
严重级别校准
严重
- - 在异步上下文中未使用 spawn_blocking 进行阻塞 I/O(std::fs::read、std::net::TcpStream)
- 在 .await 点持有互斥锁守卫(死锁风险)
- 在异步函数中使用 std::thread::sleep(阻塞运行时线程)
- 在需要背压时使用无界通道(OOM 风险)
主要
- - JoinHandle 静默丢弃(丢失错误、僵尸任务)
- 缺少 select! 取消安全性考虑
- 针对用例使用了错误的互斥锁类型(std 与 tokio)
- 网络/外部操作缺少超时
次要
- - 对微不足道的小型异步块使用 tokio::spawn(开销大于收益)
- 无正当理由使用过大的通道缓冲区
- 在 #[tokio::main] 足够的情况下手动构建运行时
- 在竞争程度高到足以受益于 tokio 异步互斥锁时使用 std::sync::Mutex
信息性
- - 建议使用 tokio-util 工具(例如 CancellationToken)
- 用于服务组合的 Tower 中间件模式
- 使用 JoinSet 的结构化并发
- 从 async-trait crate 迁移到 trait 中的原生 async fn
- 从 oncecell / lazystatic 迁移到 std::sync::LazyLock
- 使用 #[expect(lint)] 而非 #[allow(lint)] 实现自清理抑制
有效模式(不要标记)
- - std::sync::Mutex 用于短临界区 — tokio 文档推荐在锁内没有 .await 时使用此方式
- tokio::spawn 没有显式 join — 对于具有适当关闭信号的后台任务有效
- 无缓冲通道容量为 1 — 对于同步屏障有效
- 简单二进制中使用 #[tokio::main(flavor = currentthread)] — 并非每个应用都需要多线程运行时
- 在 spawn 前对 Arc 进行 clone() — 需要移入任务,并非不必要的克隆
- 大型广播通道容量 — 当滞后错误代价高昂时有效(事件溯源)
- trait 中的原生 async fn 而非 async-trait — 自 1.75 起稳定;该 crate 对于 dyn 分发场景仍然有效
- 在 -> impl Future 返回值上使用 + use — 正确的 2024 版精确捕获语法,用于限制生命周期捕获
- 对复杂异步类型使用 #[expect(clippy::typecomplexity)] — 替代 #[allow] 的自清理方式,当不再需要抑制时发出警告
提交发现前
在报告任何问题前,加载并遵循 beagle-rust:review-verification-protocol。