Async backtrace
When debugging an asynchronous application, you often need to inspect and control its state. In a regular synchronous application, you could simply use the backtrace command. However, this command provides limited utility for applications running on an asynchronous runtime.
To address this, BugStalker introduces a suite of "asynchronous backtrace" commands. These commands give you visibility into your asynchronous runtime's state, including:
- the status of asynchronous workers and blocking threads
- Detailed information about each task in the system
- each task's current state and its own backtrace - represented as a stack of futures from the root
Available commands:
-
async backtrace
(alias:async bt
) - Displays information about Tokio async workers and blocking threads, including:- worker/blocking thread IDs
- worker local task queue information
- currently executing tasks for each worker
-
async backtrace all
(alias:async bt all
) - similar to the above, but includes information about all tasks in the system. Each task is represented with:- a unique ID
- a futures stack showing the chain of dependencies (where one future awaits another)
-
async task <regex>
- prints all tasks with root async functions whose names match the given regular expression. If no regex is provided, displays active tasks
Usage example
Consider this Rust program implementing a TCP echo server using tokio
:
#[tokio::main(worker_threads = 3)]
async fn main() -> Result<(), Box<dyn Error>> {
let addr = env::args()
.nth(1)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
let listener = TcpListener::bind(&addr).await?;
println!("Listening on: {}", addr);
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = vec![0; 1024];
loop {
let n = socket
.read(&mut buf)
.await
.expect("failed to read data from socket");
if n == 0 {
return;
}
let (tx, rx) = tokio::sync::oneshot::channel();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(20)).await;
tx.send(1).unwrap();
});
tokio::time::sleep(Duration::from_secs(5)).await;
_ = rx.await;
socket
.write_all(&buf[0..n])
.await
.expect("failed to write data to socket");
}
});
}
}
To examine the state of asynchronous execution, lets set a breakpoint at the line tokio::time::sleep(Duration::from_secs(5)).await;
: