Announcing async-backtrace
October 27, 2022
Today, we are happy to announce the initial release of async-backtrace, a crate that enables you to efficiently track and view the state of asynchronous tasks in your application.
In synchronous, multi-threaded applications, you can investigate deadlocks by
inspecting stack traces of all running threads. Unfortunately, this approach
breaks down for most asynchronous Rust applications, since suspended tasks —
tasks that are not actively being polled — are invisible to traditional stack
traces. The async-backtrace
crate fills this gap, allowing you see the state
of these hidden tasks. It is architected to be highly efficient without
configuration, and is suitable for deployment in production environments.
This crate complements (but is not yet integrated with) the tracing
library
and tokio-console
. Use async-backtrace
for a birds-eye view of task state
in your application, and tracing
for isolating the inputs that lead to that
state. If your application uses the tokio runtime, you can use tokio-console
for a deeper look into your application's interactions with tokio's
synchronization primitives.
Getting Started
To use async-backtrace
, first add the crate to your Cargo.toml
file:
[dependencies]
async-backtrace = "0.2"
Then, to include your async fn
s in async task traces, simply annotate them
with #[async_backtrace::framed]
and call taskdump_tree
to receive
pretty-printed trees of your application's tasks. For instance:
#[tokio::main(flavor = "current_thread")]
async fn main() {
tokio::select! {
// run the following branches in order of their appearance
biased;
// spawn task #1
_ = tokio::spawn(foo()) => { unreachable!() }
// spawn task #2
_ = tokio::spawn(foo()) => { unreachable!() }
// print the running tasks
_ = tokio::spawn(async {}) => {
println!("{}", async_backtrace::taskdump_tree(true));
}
};
}
#[async_backtrace::framed]
async fn foo() {
bar().await;
}
#[async_backtrace::framed]
async fn bar() {
baz().await;
}
#[async_backtrace::framed]
async fn baz() {
std::future::pending::<()>().await
}
Running the above example prints the trees:
╼ multiple::foo::{{closure}} at backtrace/examples/multiple.rs:22:1
└╼ multiple::bar::{{closure}} at backtrace/examples/multiple.rs:27:1
└╼ multiple::baz::{{closure}} at backtrace/examples/multiple.rs:32:1
╼ multiple::foo::{{closure}} at backtrace/examples/multiple.rs:22:1
└╼ multiple::bar::{{closure}} at backtrace/examples/multiple.rs:27:1
└╼ multiple::baz::{{closure}} at backtrace/examples/multiple.rs:32:1
See here for more examples!
Feedback Welcome
This launch is only an initial release. Work on async-backtrace
has just
begun. To guide our development, we need your feedback. So, give it a shot, and
let us know how it goes. Please file issues and
ping us on Discord.