mirror of
https://github.com/tokio-rs/tokio.git
synced 2025-09-25 12:00:35 +00:00
tokio: improve task dump documentation (#5778)
Adds depth to the taskdump example, and documentation to Handle::dump.
This commit is contained in:
parent
7ccd3e0c6d
commit
cb18b0a231
@ -1,4 +1,6 @@
|
||||
//! This example demonstrates tokio's experimental taskdumping functionality.
|
||||
//! This example demonstrates tokio's experimental task dumping functionality.
|
||||
//! This application deadlocks. Input CTRL+C to display traces of each task, or
|
||||
//! input CTRL+C twice within 1 second to quit.
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
@ -7,44 +9,74 @@
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use std::hint::black_box;
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Barrier;
|
||||
|
||||
#[inline(never)]
|
||||
async fn a() {
|
||||
black_box(b()).await
|
||||
async fn a(barrier: Arc<Barrier>) {
|
||||
b(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn b() {
|
||||
black_box(c()).await
|
||||
async fn b(barrier: Arc<Barrier>) {
|
||||
c(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn c() {
|
||||
loop {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
async fn c(barrier: Arc<Barrier>) {
|
||||
barrier.wait().await;
|
||||
}
|
||||
|
||||
async fn dump() {
|
||||
// Prints a task dump upon receipt of CTRL+C, or returns if CTRL+C is
|
||||
// inputted twice within a second.
|
||||
async fn dump_or_quit() {
|
||||
use tokio::time::{timeout, Duration, Instant};
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let dump = handle.dump().await;
|
||||
let mut last_signal: Option<Instant> = None;
|
||||
// wait for CTRL+C
|
||||
while let Ok(_) = tokio::signal::ctrl_c().await {
|
||||
// exit if a CTRL+C is inputted twice within 1 second
|
||||
if let Some(time_since_last_signal) = last_signal.map(|i| i.elapsed()) {
|
||||
if time_since_last_signal < Duration::from_secs(1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
last_signal = Some(Instant::now());
|
||||
|
||||
for (i, task) in dump.tasks().iter().enumerate() {
|
||||
let trace = task.trace();
|
||||
println!("task {i} trace:");
|
||||
println!("{trace}\n");
|
||||
// capture a dump, and print each trace
|
||||
println!("{:-<80}", "");
|
||||
if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
|
||||
for (i, task) in dump.tasks().iter().enumerate() {
|
||||
let trace = task.trace();
|
||||
println!("TASK {i}:");
|
||||
println!("{trace}\n");
|
||||
}
|
||||
} else {
|
||||
println!("Task dumping timed out. Use a native debugger (like gdb) to debug the deadlock.");
|
||||
}
|
||||
println!("{:-<80}", "");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("This program has a deadlock.");
|
||||
println!("Input CTRL+C to print a task dump.");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
|
||||
// oops! this barrier waits for one more task than will ever come.
|
||||
let barrier = Arc::new(Barrier::new(3));
|
||||
|
||||
let task_1 = tokio::spawn(a(barrier.clone()));
|
||||
let task_2 = tokio::spawn(a(barrier));
|
||||
|
||||
tokio::select!(
|
||||
biased;
|
||||
_ = tokio::spawn(a()) => {},
|
||||
_ = tokio::spawn(b()) => {},
|
||||
_ = tokio::spawn(c()) => {},
|
||||
_ = dump() => {},
|
||||
_ = dump_or_quit() => {},
|
||||
_ = task_1 => {},
|
||||
_ = task_2 => {},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
|
@ -1,26 +1,36 @@
|
||||
//! Snapshots of runtime state.
|
||||
//!
|
||||
//! See [Handle::dump][crate::runtime::Handle::dump].
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// A snapshot of a runtime's state.
|
||||
///
|
||||
/// See [Handle::dump][crate::runtime::Handle::dump].
|
||||
#[derive(Debug)]
|
||||
pub struct Dump {
|
||||
tasks: Tasks,
|
||||
}
|
||||
|
||||
/// Snapshots of tasks.
|
||||
///
|
||||
/// See [Handle::dump][crate::runtime::Handle::dump].
|
||||
#[derive(Debug)]
|
||||
pub struct Tasks {
|
||||
tasks: Vec<Task>,
|
||||
}
|
||||
|
||||
/// A snapshot of a task.
|
||||
///
|
||||
/// See [Handle::dump][crate::runtime::Handle::dump].
|
||||
#[derive(Debug)]
|
||||
pub struct Task {
|
||||
trace: Trace,
|
||||
}
|
||||
|
||||
/// An execution trace of a task's last poll.
|
||||
///
|
||||
/// See [Handle::dump][crate::runtime::Handle::dump].
|
||||
#[derive(Debug)]
|
||||
pub struct Trace {
|
||||
inner: super::task::trace::Trace,
|
||||
|
@ -373,7 +373,77 @@ cfg_metrics! {
|
||||
|
||||
cfg_taskdump! {
|
||||
impl Handle {
|
||||
/// Capture a snapshot of this runtime's state.
|
||||
/// Captures a snapshot of the runtime's state.
|
||||
///
|
||||
/// This functionality is experimental, and comes with a number of
|
||||
/// requirements and limitations.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This can be used to get call traces of each task in the runtime.
|
||||
/// Calls to `Handle::dump` should usually be enclosed in a
|
||||
/// [timeout][crate::time::timeout], so that dumping does not escalate a
|
||||
/// single blocked runtime thread into an entirely blocked runtime.
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio::runtime::Runtime;
|
||||
/// # fn dox() {
|
||||
/// # let rt = Runtime::new().unwrap();
|
||||
/// # rt.spawn(async {
|
||||
/// use tokio::runtime::Handle;
|
||||
/// use tokio::time::{timeout, Duration};
|
||||
///
|
||||
/// // Inside an async block or function.
|
||||
/// let handle = Handle::current();
|
||||
/// if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
|
||||
/// for (i, task) in dump.tasks().iter().enumerate() {
|
||||
/// let trace = task.trace();
|
||||
/// println!("TASK {i}:");
|
||||
/// println!("{trace}\n");
|
||||
/// }
|
||||
/// }
|
||||
/// # });
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Requirements
|
||||
///
|
||||
/// ## Debug Info Must Be Available
|
||||
/// To produce task traces, the application must **not** be compiled
|
||||
/// with split debuginfo. On Linux, including debuginfo within the
|
||||
/// application binary is the (correct) default. You can further ensure
|
||||
/// this behavior with the following directive in your `Cargo.toml`:
|
||||
///
|
||||
/// ```toml
|
||||
/// [profile.*]
|
||||
/// split-debuginfo = "off"
|
||||
/// ```
|
||||
///
|
||||
/// ## Platform Requirements
|
||||
///
|
||||
/// Task dumps are supported on Linux atop x86 and x86_64.
|
||||
///
|
||||
/// ## Current Thread Runtime Requirements
|
||||
///
|
||||
/// On the `current_thread` runtime, task dumps may only be requested
|
||||
/// from *within* the context of the runtime being dumped. Do not, for
|
||||
/// example, await `Handle::dump()` on a different runtime.
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
/// ## Local Executors
|
||||
///
|
||||
/// Tasks managed by local executors (e.g., `FuturesUnordered` and
|
||||
/// [`LocalSet`][crate::task::LocalSet]) may not appear in task dumps.
|
||||
///
|
||||
/// ## Non-Termination When Workers Are Blocked
|
||||
///
|
||||
/// The future produced by `Handle::dump` may never produce `Ready` if
|
||||
/// another runtime worker is blocked for more than 250ms. This may
|
||||
/// occur if a dump is requested during shutdown, or if another runtime
|
||||
/// worker is infinite looping or synchronously deadlocked. For these
|
||||
/// reasons, task dumping should usually be paired with an explicit
|
||||
/// [timeout][crate::time::timeout].
|
||||
pub async fn dump(&self) -> crate::runtime::Dump {
|
||||
match &self.inner {
|
||||
scheduler::Handle::CurrentThread(handle) => handle.dump(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user