tokio-async-code-review について
tokio-async-code-reviewは、Tokioを使うRust非同期コードのレビュー向けスキルです。ランタイム、ブロッキング処理、タスク、同期プリミティブ、チャネルを扱い、指摘には根拠を求めます。
主な機能
- 01Tokio の依存関係と機能フラグの確認
- 02ランタイムモデルの特定
- 03非同期コード内のブロッキング処理の調査
- 04タスクと同期プリミティブのレビュー
- 05チャネル形式とキャンセル安全性の確認
活用シーン
- 01Tokio の機能フラグとランタイム設定のレビュー
- 02非同期関数内のブロッキング呼び出しの発見
- 03タスクハンドルとエラー処理の確認
- 04ロック、チャネル、キャンセル動作の評価
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ existential-birds/ beagle --skill tokio-async-code-reviewインストール後、Claude Code で/tokio-async-code-reviewを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/channels.mdpinning-cancellation.mdsync-primitives.mdtask-management.md詳細
Tokio の非同期コードレビュー
レビューワークフロー
- Cargo.toml を確認する — tokio のフィーチャーフラグ(
full、rt-multi-thread、macros、syncなど)を確認します。フィーチャーが不足していると、原因の分かりにくいコンパイルエラーが発生します。 - ランタイムの設定を確認する —
#[tokio::main]と手動でのランタイム構築のどちらが使われていますか? マルチスレッドとカレントスレッドのどちらですか? - ブロッキング処理を探す — 非同期関数内で
std::fs、std::net、std::thread::sleep、CPU 負荷の高いループを検索します。 - チャネルの使い方を確認する — 通信パターンに適したチャネルの種類(mpsc、broadcast、oneshot、watch)を選びます。
- 同期プリミティブを確認する — 正しい種類のミューテックスが使われていること、ガードのライフタイムが適切であること、デッドロックの可能性がないことを確認します。
ゲート(結論を出す前に満たすべき客観的な条件)
レビュー対象の範囲について、順番に完了してください。該当するゲートを通過するまでは、Critical または Major と断定しないでください。
- 依存関係の確認 —
tokioを提供するクレートのCargo.toml(継承されている場合はワークスペースのものも)を読みます。合格条件:tokioのバージョンと有効なフィーチャーを記したメモがあること、またはtokioへの直接依存がないことと、その依存元(ワークスペース/パス)を明記していること。 - ランタイムモデル — ランタイムの構築箇所(
#[tokio::main]、Runtime::builder、テスト、またはランタイムを所有しないライブラリ)を特定します。合格条件: フレーバー(multi_thread/current_thread/テストのみ/なし)と、その定義箇所を1行で示していること。 - ブロッキング処理の一覧 — レビュー対象のパスで、ブロッキング API(
std::fs::、非同期ラッパーのないstd::net::、std::thread::sleep、async fn内の高負荷な CPU ループ)を検索します。合格条件: 検出箇所をそれぞれpath:line形式(またはツールの出力抜粋)で列挙していること。または、検索後に「レビュー対象の非同期コードにブロッキングパターンは見つかりませんでした」と明記していること。 - プロトコル — review-verification-protocol スキルを読み込みます。合格条件: 指摘を報告する前に、そのスキルの合格条件を満たしていること(主張する問題について
file:lineの根拠を示すこと)。
出力形式
指摘事項は次の形式で報告してください。
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
クイックリファレンス
| 問題の種類 | 参照先 |
|---|---|
| タスクの生成、JoinHandle、構造化並行性 | references/task-management.md |
| Mutex、RwLock、Semaphore、Notify、Barrier | references/sync-primitives.md |
| mpsc、broadcast、oneshot、watch のチャネルパターン | references/channels.md |
| Pin、キャンセル、Future の内部構造、select!、ブロッキング処理との橋渡し | references/pinning-cancellation.md |
レビューチェックリスト
ランタイムの設定
- Cargo.toml の Tokio フィーチャーが実際の使用状況と一致している
- ランタイムのフレーバーがワークロードに適している(I/O バウンドには
multi_thread、より単純なケースにはcurrent_thread) - 非同期テストには
#[tokio::test]を使用している(手動でランタイムを構築していない) - 本番環境に適したワーカースレッド数を設定している
タスク管理
-
spawnの戻り値(JoinHandle)を追跡しており、暗黙に破棄していない - CPU 負荷の高い処理や同期 I/O 処理には
spawn_blockingを使用している - タスクがキャンセルに対応している(
CancellationToken、select!、またはシャットダウンチャネルを使用) -
JoinError(タスクのパニックまたはキャンセル)を単にアンラップせず、適切に処理している -
tokio::select!の各分岐がキャンセルセーフである - 可能な場合は
async-traitクレートではなく、トレイトでネイティブのasync fnを使用している(Rust 1.75 以降で安定) - 非同期コンテキストにおける RPIT のライフタイムキャプチャを確認している — edition 2024 では
-> impl Futureがスコープ内のすべてのライフタイムをキャプチャするようになりました
同期プリミティブ
- ロックを
.awaitをまたいで保持する場合はtokio::sync::Mutexを使用し、短い非同期処理ではないセクションではstd::sync::Mutexを使用する - await ポイントをまたいでミューテックスガードを保持しない(デッドロックのリスク)
- 同時実行操作の制限には
Semaphoreを使用する(場当たり的なカウンターは使用しない) - 読み取りが多いワークロード(読み取りが多く、書き込みの頻度が低い)では
RwLockを使用する - シンプルなシグナリングには
Notifyを使用する(チャネルのオーバーヘッドは避ける) - 実行時に初期化されるシングルトンには、
once_cell::sync::Lazyやlazy_static!の代わりにstd::sync::LazyLockを使用する(Rust 1.80 以降で安定版) - エディション 2024 の一時値のスコープに照らして
if letのロックガードパターンを確認する。一時値の破棄が早まり、借用の有効性が変わる場合がある
チャネル
- パターンに合ったチャネル型を使用する:バックプレッシャーには mpsc、ファンアウトには broadcast、リクエスト/レスポンスには oneshot、最新値には watch
- 有界チャネルの容量が適切である(小さすぎるとデッドロック、大きすぎるとメモリを消費する)
-
SendError/RecvErrorを処理する(相手側がドロップされたことを示す) - Broadcast の
Laggedエラーを処理する(受信側が遅れている) - 受信側に完了を知らせるため、処理が終わったらチャネルの送信側をドロップする
タイマーとスリープ
-
std::thread::sleepの代わりにtokio::time::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クレートから、トレイト内のネイティブなasync fnへの移行once_cell/lazy_staticからstd::sync::LazyLockへの移行- 抑制を自己解消可能にするため、
#[allow(lint)]の代わりに#[expect(lint)]を使用する
有効なパターン(指摘しないこと)
- 短いクリティカルセクションでの
std::sync::Mutex— ロック内に.awaitがない場合、tokio のドキュメントでも推奨されています - 明示的な join を伴わない
tokio::spawn— 適切なシャットダウンシグナリングがあれば、バックグラウンドタスクとして有効です - 容量 1 のバッファーなしチャネル — 同期バリアとして有効です
- シンプルなバイナリでの
#[tokio::main(flavor = "current_thread")]— すべてのアプリでマルチスレッドランタイムが必要なわけではありません - spawn の前に行う
Arc<T>のclone()— タスク内へ移動するために必要であり、不要な複製ではありません - 大容量の broadcast チャネル — lagged エラーのコストが高い場合(イベントソーシングなど)に有効です
async-traitを使わないトレイト内のネイティブなasync fn— 1.75 以降で安定版です。dynディスパッチの場合には、引き続きこのクレートが有効です-> impl Futureの戻り値に付ける+ use<'a>— ライフタイムのキャプチャを制限する、エディション 2024 の正しい厳密なキャプチャ構文です- 複雑な非同期型に対する
#[expect(clippy::type_complexity)]—#[allow]の代わりとなる自己解消可能な方法であり、抑制が不要になった際に警告します
指摘を提出する前に
Gates の後、報告するすべての問題に review-verification-protocol スキルを適用してください(証拠と処置はそのスキルに従ってください)。
コメント
Markdown 対応