tokio-blocked integrates with Tokio's tracing feature to detect tasks
that are blocked by synchronous or CPU-heavy code,
and surfaces information to developers through log messages or data dumps.
One of the most common mistakes in async Rust code is running synchronous blocking operations or CPU-heavy code inside async tasks.
The general recommendation is to offload all code that takes more than
10–100 microseconds by using tokio::task::spawn_blocking.
Not doing this can lead to mysterious latency spikes, stalls, and degraded performance that only show up under load or in production.
If you prefer examples, just jump into the example directory
and execute the ./run.sh script.
NOTE: The tracing feature in Tokio is experimental (as of Tokio 1.47).
To enable it, set the environment variable RUSTFLAGS="--cfg tokio_unstable"
when building.
To use tokio-blocked, follow these steps:
In Cargo.toml:
[dependencies]
# Enable the tracing feature for Tokio
tokio = { version = "1", features = ["tracing", "rt-multi-thread", "macros"] }
# Depend on tokio-blocked and the tracing crates
tokio-blocked = "*"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["fmt", "env-filter"] }In main.rs:
use std::time::Duration;
use tokio_blocked::TokioBlockedLayer;
use tracing_subscriber::{
EnvFilter, Layer as _, layer::SubscriberExt as _, util::SubscriberInitExt as _,
};
#[tokio::main]
async fn main() {
// Prepare the tracing-subscriber logger with both a regular format logger
// and the TokioBlockedLayer.
{
let fmt = tracing_subscriber::fmt::layer().with_filter(EnvFilter::from_default_env());
let blocked = TokioBlockedLayer::new()
.with_warn_busy_single_poll(Some(Duration::from_micros(150)));
tracing_subscriber::registry()
.with(fmt)
.with(blocked)
.init();
}
tokio::task::spawn(async {
// BAD!
// This produces a warning log message.
std::thread::sleep(Duration::from_secs(2));
})
.await
.unwrap();
}Now the code can be run with:
RUSTFLAGS="--cfg tokio_unstable" RUST_LOG=warn cargo runYou will see a log message like this:
2025-08-23T06:40:30.860946Z WARN tokio_blocked::task_poll_blocked: poll_duration_ns=2000394057 callsite.name="runtime.spawn" callsite.target="tokio::task" callsite.file="src/main.rs" callsite.line=24 callsite.col=5
TODO
-
Blocking code location:
Tokio can only capture the location of the
tokio::task::spawncallsite, so that is all the informationtokio-blockedcan provide. If you have a large future that can take many different code paths, like in a web server with many routes, it can be very hard to find the exact location of the blocking code.A workaround is to wrap potentially problematic code in
tokio::spawn(...).awaitno narrow down the location.
Thanks to tokio-console for examples of extracting information from the Tokio trace data.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.