Debugging and troubleshooting Tokio applications using tokio-console, detecting deadlocks, memory leaks, and performance issues. Use when diagnosing async runtime problems.
View on GitHubgeoffjay/claude-plugins
rust-tokio-expert
January 20, 2026
Select agents to install to:
npx add-skill https://github.com/geoffjay/claude-plugins/blob/main/plugins/rust-tokio-expert/skills/tokio-troubleshooting/SKILL.md -a claude-code --skill tokio-troubleshootingInstallation paths:
.claude/skills/tokio-troubleshooting/# Tokio Troubleshooting
This skill provides techniques for debugging and troubleshooting async applications built with Tokio.
## Using tokio-console for Runtime Inspection
Monitor async runtime in real-time:
```rust
// In Cargo.toml
[dependencies]
console-subscriber = "0.2"
// In main.rs
fn main() {
console_subscriber::init();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
run_application().await
});
}
```
**Run console in separate terminal:**
```bash
tokio-console
```
**Key metrics to monitor:**
- Task spawn rate and total tasks
- Poll duration per task
- Idle vs. busy time
- Waker operations
- Resource utilization
**Identifying issues:**
- Long poll durations: CPU-intensive work in async context
- Many wakers: Potential contention or inefficient polling
- Growing task count: Task leak or unbounded spawning
- High idle time: Not enough work or blocking operations
## Debugging Deadlocks and Hangs
Detect and resolve deadlock situations:
### Common Deadlock Pattern
```rust
// BAD: Potential deadlock
async fn deadlock_example() {
let mutex1 = Arc::new(Mutex::new(()));
let mutex2 = Arc::new(Mutex::new(()));
let m1 = mutex1.clone();
let m2 = mutex2.clone();
tokio::spawn(async move {
let _g1 = m1.lock().await;
tokio::time::sleep(Duration::from_millis(10)).await;
let _g2 = m2.lock().await; // May deadlock
});
let _g2 = mutex2.lock().await;
tokio::time::sleep(Duration::from_millis(10)).await;
let _g1 = mutex1.lock().await; // May deadlock
}
// GOOD: Consistent lock ordering
async fn no_deadlock_example() {
let mutex1 = Arc::new(Mutex::new(()));
let mutex2 = Arc::new(Mutex::new(()));
// Always acquire locks in same order
let _g1 = mutex1.lock().await;
let _g2 = mutex2.lock().await;
}
// BETTER: Avoid nested locks
async fn best_example() {
// Use message passing instead