rust-async-internals

Diagnose and fix tokio task, cancellation, and Send-bound issues in Rust async code.

2|1|Updated Aug 19, 2026
One-click install
npx skills add https://github.com/po4yka/rust-skills --skill rust-async-internals-po4yka
Or copy as Structured Prompt for Agent▼
Please help me install this Agent Skill.
Skill: rust-async-internals
Source: https://github.com/po4yka/rust-skills/tree/main/skills/rust-async-internals
Command: npx skills add https://github.com/po4yka/rust-skills --skill rust-async-internals-po4yka

SYSTEM DOCUMENTATION & REQUIREMENTS

💡 This Skill includes references (resource) components.

What problem does it solve? Rust async code fails in ways the compiler and lints do not catch: shutdown hangs from select! loops without a cancelled() arm, lost work from non-cancel-safe futures dropped by select! or timeout, deadlocks from std::sync::Mutex guards held across .await, and unnumbered "future cannot be sent between threads safely" errors from AsyncFn callbacks. This Skill provides decision tables, triage tables, and verification commands to write, review, and debug tokio-based async code correctly. ## Core Features & Use Cases - Decision and triage tables: Map symptoms like shutdown hangs, task stalls, broadcast Lagged data loss, and blocking-pool saturation to concrete causes and fixes, covering select!, JoinSet, TaskTracker, CancellationToken, spawn_blocking, and block_in_place. - Cancel-safety discipline: Enforces cancel-safe:/NOT cancel-safe: annotations on futures used in select!, timeout, or FuturesUnordered, with a library-method cancel-safety table and a spawn-and-join firewall pattern for atomic critical sections. - Async bounds guidance: Resolves Send, 'static, and dyn-compatibility failures for AsyncFn closures, async fn in traits, RPIT captures in edition 2024, and concurrent futures over shared state. - Use Case: A service hangs on shutdown in production. The triage table points to a select! loop missing a cancelled() arm; the fix adds biased; plus a CancellationToken arm, and a bounded-shutdown test with tokio::time::timeout verifies it. ## Quick Start Ask the agent to review your tokio select! loop or spawned-task shutdown path for cancel safety and blocking calls using the rust-async-internals skill.

Frequently Asked Questions about rust-async-internals

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I make a tokio select! loop shut down cleanly?▼

Add a `_ = cancel.cancelled() => break` arm with a CancellationToken and put `biased;` first so shutdown is polled before data arms. Without a cancelled() arm, a long-lived select! loop hangs on shutdown.

What does cancel safe mean for a Rust async future?▼

A future is cancel safe if dropping it between any two .await points leaves observable state consistent. No type or lint expresses this, so annotate futures used in select!, timeout, or FuturesUnordered with cancel-safe: or NOT cancel-safe: comments.

Why does tokio::spawn fail with future cannot be sent between threads safely?▼

One !Send value held across an .await makes the whole future !Send, commonly a std::sync::MutexGuard or RefCell borrow. Drop the guard before the .await, or use tokio::sync::Mutex when the lock must cross it.

When should I use spawn_blocking vs block_in_place vs std::thread?▼

Use spawn_blocking for bounded CPU work or short blocking syscalls, and std::thread::spawn for indefinite blocking loops that would occupy a pool thread forever. block_in_place panics on a current_thread runtime and starves join! branches, so prefer spawn_blocking.

Why does tokio broadcast lose messages without an error?▼

A slow receiver falls behind the fixed ring buffer and its next recv() returns Err(RecvError::Lagged(n)); a `while let Ok(..)` loop then ends silently. Match Lagged explicitly, or switch to mpsc for lossless delivery.

Does tokio::time::timeout stop CPU-bound work?▼

No. timeout polls the wrapped future before checking the deadline and cannot preempt one poll, so timeout(d, async { cpu_work() }) does not bound the work. Wrap it as timeout(d, spawn_blocking(work)) and pass a CancellationToken into the closure for cooperative stopping.