| Crates.io | no-block-pls |
| lib.rs | no-block-pls |
| version | 0.1.0 |
| created_at | 2025-12-03 15:56:36.191752+00 |
| updated_at | 2025-12-03 15:56:36.191752+00 |
| description | Instrument async Rust code to surface blocking work between await points |
| homepage | |
| repository | https://github.com/0xdeafbeef/no-block-pls |
| max_upload_size | |
| id | 1964393 |
| size | 102,456 |
Detect blocking work between async suspension points by instrumenting your Rust sources and logging slow sections.
Tokio console and similar tools watch futures from the runtime’s point of view.
When your whole app collapses into one mega-future (e.g., async fn main →
server → pipeline), a blocking spot shows up as something vague like
SpawnLocation:main.rs:35. Helpful, but not precise.
You can't obtain this information from the runtime alone, because runtime hands over control to poll, and it can't see what happens inside.
no-block-pls walks your source tree, finds every async function, and times
the
synchronous sections between awaits. It measures from function entry
to the first await, then from one await boundary to the next, so you see exactly
which chunk of sync work is slow.
Instant::now() takes 100 cycles, so overhead is negligible compared to
other async work.
It would be great to have a compiler plugin that does this to add
instrumentation during
async fn -> state machine conversion, but that’s not possible (yet?)
tracing::warn!, it can be changed, but it's not here.cargo install --git https://github.com/0xdeafbeef/no-block-pls
# or
cargo install no-block-pls
# instrument in-place (creates .rs.bak backups next to originals)
no-block-pls -i
# run your app as usual and watch logs
cargo run --release
# restore backups if needed
no-block-pls -r
Example log output:

What you'll see when a section blocks (10 ms default):
WARN long poll elapsed_ms=237 name=my_crate::handlers::fetch_and_process span=src/handlers.rs:12-18 hits=1 wraparound=false
lib.rs/main.rs.Input:
async fn fetch_and_process() {
let data = fetch().await;
process(data);
}
Instrumented:
async fn fetch_and_process() {
let mut __guard = crate::__async_profile_guard__::Guard::new(
concat!(module_path!(), "::", stringify!(fetch_and_process)),
file!(),
1u32,
);
let data = {
__guard.end_section(2u32);
let __result = fetch().await;
__guard.start_section(2u32);
__result
};
process(data);
}
.rs.bak backups.*.rs.bak back over the originals.tracing::warn!; set your subscriber accordingly.Drop
impl.