Skip to content

Commit 660eac7

Browse files
authored
taskdump: implement task dumps for current-thread runtime (tokio-rs#5608)
Task dumps are snapshots of runtime state. Taskdumps are collected by instrumenting Tokio's leaves to conditionally collect backtraces, which are then coalesced per-task into execution tree traces. This initial implementation only supports collecting taskdumps from within the context of a current-thread runtime, and only `yield_now()` is instrumented.
1 parent 1d785fd commit 660eac7

23 files changed

+943
-13
lines changed

.github/workflows/ci.yml

+42-10
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,10 @@ jobs:
186186
runs-on: ${{ matrix.os }}
187187
strategy:
188188
matrix:
189-
os:
190-
- windows-latest
191-
- ubuntu-latest
192-
- macos-latest
189+
include:
190+
- os: windows-latest
191+
- os: ubuntu-latest
192+
- os: macos-latest
193193
steps:
194194
- uses: actions/checkout@v3
195195
- name: Install Rust ${{ env.rust_stable }}
@@ -206,6 +206,31 @@ jobs:
206206
# in order to run doctests for unstable features, we must also pass
207207
# the unstable cfg to RustDoc
208208
RUSTDOCFLAGS: --cfg tokio_unstable
209+
210+
test-unstable-taskdump:
211+
name: test tokio full --unstable --taskdump
212+
needs: basics
213+
runs-on: ${{ matrix.os }}
214+
strategy:
215+
matrix:
216+
include:
217+
- os: ubuntu-latest
218+
steps:
219+
- uses: actions/checkout@v3
220+
- name: Install Rust ${{ env.rust_stable }}
221+
uses: dtolnay/rust-toolchain@master
222+
with:
223+
toolchain: ${{ env.rust_stable }}
224+
- uses: Swatinem/rust-cache@v2
225+
# Run `tokio` with "unstable" and "taskdump" cfg flags.
226+
- name: test tokio full --cfg unstable --cfg taskdump
227+
run: cargo test --all-features
228+
working-directory: tokio
229+
env:
230+
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
231+
# in order to run doctests for unstable features, we must also pass
232+
# the unstable cfg to RustDoc
233+
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_taskdump
209234

210235
miri:
211236
name: miri
@@ -293,9 +318,11 @@ jobs:
293318
matrix:
294319
include:
295320
- target: i686-unknown-linux-gnu
321+
rustflags: --cfg tokio_taskdump
296322
- target: arm-unknown-linux-gnueabihf
297323
- target: armv7-unknown-linux-gnueabihf
298324
- target: aarch64-unknown-linux-gnu
325+
rustflags: --cfg tokio_taskdump
299326

300327
# Run a platform without AtomicU64 and no const Mutex::new
301328
- target: arm-unknown-linux-gnueabihf
@@ -341,15 +368,15 @@ jobs:
341368
target: i686-unknown-linux-gnu
342369
- run: cargo test -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
343370
env:
344-
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
371+
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64
345372
# https://github.com/tokio-rs/tokio/pull/5356
346373
# https://github.com/tokio-rs/tokio/issues/5373
347374
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
348375
env:
349-
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
376+
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
350377
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
351378
env:
352-
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
379+
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64
353380

354381
features:
355382
name: features
@@ -372,6 +399,11 @@ jobs:
372399
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
373400
env:
374401
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
402+
# Try with unstable and taskdump feature flags
403+
- name: check --feature-powerset --unstable --taskdump
404+
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
405+
env:
406+
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
375407

376408
minrust:
377409
name: minrust
@@ -424,7 +456,7 @@ jobs:
424456
cargo hack check --all-features --ignore-private
425457
- name: "check --all-features --unstable -Z minimal-versions"
426458
env:
427-
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
459+
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
428460
run: |
429461
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
430462
# from determining minimal versions based on dev-dependencies.
@@ -481,8 +513,8 @@ jobs:
481513
- name: "doc --lib --all-features"
482514
run: cargo doc --lib --no-deps --all-features --document-private-items
483515
env:
484-
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
485-
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
516+
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump
517+
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
486518

487519
loom-compile:
488520
name: build loom tests

examples/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,7 @@ path = "named-pipe-ready.rs"
9090
[[example]]
9191
name = "named-pipe-multi-client"
9292
path = "named-pipe-multi-client.rs"
93+
94+
[[example]]
95+
name = "dump"
96+
path = "dump.rs"

examples/dump.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! This example demonstrates tokio's experimental taskdumping functionality.
2+
3+
#[cfg(all(
4+
tokio_unstable,
5+
tokio_taskdump,
6+
target_os = "linux",
7+
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
8+
))]
9+
#[tokio::main(flavor = "current_thread")]
10+
async fn main() {
11+
use std::hint::black_box;
12+
13+
#[inline(never)]
14+
async fn a() {
15+
black_box(b()).await
16+
}
17+
18+
#[inline(never)]
19+
async fn b() {
20+
black_box(c()).await
21+
}
22+
23+
#[inline(never)]
24+
async fn c() {
25+
black_box(tokio::task::yield_now()).await
26+
}
27+
28+
tokio::spawn(a());
29+
tokio::spawn(b());
30+
tokio::spawn(c());
31+
32+
let handle = tokio::runtime::Handle::current();
33+
let dump = handle.dump();
34+
35+
for (i, task) in dump.tasks().iter().enumerate() {
36+
let trace = task.trace();
37+
println!("task {i} trace:");
38+
println!("{trace}");
39+
}
40+
}
41+
42+
#[cfg(not(all(
43+
tokio_unstable,
44+
tokio_taskdump,
45+
target_os = "linux",
46+
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
47+
)))]
48+
fn main() {
49+
println!("task dumps are not available")
50+
}

tokio/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ socket2 = { version = "0.4.9", optional = true, features = [ "all" ] }
115115
[target.'cfg(tokio_unstable)'.dependencies]
116116
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true } # Not in full
117117

118+
# Currently unstable. The API exposed by these features may be broken at any time.
119+
# Requires `--cfg tokio_unstable` to enable.
120+
[target.'cfg(tokio_taskdump)'.dependencies]
121+
backtrace = { version = "0.3.58" }
122+
118123
[target.'cfg(unix)'.dependencies]
119124
libc = { version = "0.2.42", optional = true }
120125
signal-hook-registry = { version = "1.1.1", optional = true }

tokio/src/lib.rs

+15
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,21 @@ compile_error!("Tokio's build script has incorrectly detected wasm.");
487487
))]
488488
compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm.");
489489

490+
#[cfg(all(not(tokio_unstable), tokio_taskdump))]
491+
compile_error!("The `tokio_taskdump` feature requires `--cfg tokio_unstable`.");
492+
493+
#[cfg(all(
494+
tokio_taskdump,
495+
not(all(
496+
target_os = "linux",
497+
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
498+
))
499+
))]
500+
compile_error!(
501+
"The `tokio_taskdump` feature is only currently supported on \
502+
linux, on `aarch64`, `x86` and `x86_64`."
503+
);
504+
490505
// Includes re-exports used by macros.
491506
//
492507
// This module is not intended to be part of the public API. In general, any

tokio/src/macros/cfg.rs

+30
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,36 @@ macro_rules! cfg_not_rt_multi_thread {
373373
}
374374
}
375375

376+
macro_rules! cfg_taskdump {
377+
($($item:item)*) => {
378+
$(
379+
#[cfg(all(
380+
tokio_unstable,
381+
tokio_taskdump,
382+
feature = "rt",
383+
target_os = "linux",
384+
any(
385+
target_arch = "aarch64",
386+
target_arch = "x86",
387+
target_arch = "x86_64"
388+
)
389+
))]
390+
#[cfg_attr(docsrs, doc(cfg(all(
391+
tokio_unstable,
392+
tokio_taskdump,
393+
feature = "rt",
394+
target_os = "linux",
395+
any(
396+
target_arch = "aarch64",
397+
target_arch = "x86",
398+
target_arch = "x86_64"
399+
)
400+
))))]
401+
$item
402+
)*
403+
};
404+
}
405+
376406
macro_rules! cfg_test_util {
377407
($($item:item)*) => {
378408
$(

tokio/src/runtime/context.rs

+34
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ cfg_rt! {
1212
use std::cell::RefCell;
1313
use std::marker::PhantomData;
1414
use std::time::Duration;
15+
16+
cfg_taskdump! {
17+
use crate::runtime::task::trace;
18+
}
1519
}
1620

1721
struct Context {
@@ -45,6 +49,15 @@ struct Context {
4549
/// Tracks the amount of "work" a task may still do before yielding back to
4650
/// the sheduler
4751
budget: Cell<coop::Budget>,
52+
53+
#[cfg(all(
54+
tokio_unstable,
55+
tokio_taskdump,
56+
feature = "rt",
57+
target_os = "linux",
58+
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
59+
))]
60+
trace: trace::Context,
4861
}
4962

5063
tokio_thread_local! {
@@ -75,6 +88,19 @@ tokio_thread_local! {
7588
rng: FastRand::new(RngSeed::new()),
7689

7790
budget: Cell::new(coop::Budget::unconstrained()),
91+
92+
#[cfg(all(
93+
tokio_unstable,
94+
tokio_taskdump,
95+
feature = "rt",
96+
target_os = "linux",
97+
any(
98+
target_arch = "aarch64",
99+
target_arch = "x86",
100+
target_arch = "x86_64"
101+
)
102+
))]
103+
trace: trace::Context::new(),
78104
}
79105
}
80106
}
@@ -378,6 +404,14 @@ cfg_rt! {
378404
matches!(self, EnterRuntime::Entered { .. })
379405
}
380406
}
407+
408+
cfg_taskdump! {
409+
/// SAFETY: Callers of this function must ensure that trace frames always
410+
/// form a valid linked list.
411+
pub(crate) unsafe fn with_trace<R>(f: impl FnOnce(&trace::Context) -> R) -> R {
412+
CONTEXT.with(|c| f(&c.trace))
413+
}
414+
}
381415
}
382416

383417
// Forces the current "entered" state to be cleared while the closure

tokio/src/runtime/dump.rs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//! Snapshots of runtime state.
2+
3+
use std::fmt;
4+
5+
/// A snapshot of a runtime's state.
6+
#[derive(Debug)]
7+
pub struct Dump {
8+
tasks: Tasks,
9+
}
10+
11+
/// Snapshots of tasks.
12+
#[derive(Debug)]
13+
pub struct Tasks {
14+
tasks: Vec<Task>,
15+
}
16+
17+
/// A snapshot of a task.
18+
#[derive(Debug)]
19+
pub struct Task {
20+
trace: Trace,
21+
}
22+
23+
/// An execution trace of a task's last poll.
24+
#[derive(Debug)]
25+
pub struct Trace {
26+
inner: super::task::trace::Trace,
27+
}
28+
29+
impl Dump {
30+
pub(crate) fn new(tasks: Vec<Task>) -> Self {
31+
Self {
32+
tasks: Tasks { tasks },
33+
}
34+
}
35+
36+
/// Tasks in this snapshot.
37+
pub fn tasks(&self) -> &Tasks {
38+
&self.tasks
39+
}
40+
}
41+
42+
impl Tasks {
43+
/// Iterate over tasks.
44+
pub fn iter(&self) -> impl Iterator<Item = &Task> {
45+
self.tasks.iter()
46+
}
47+
}
48+
49+
impl Task {
50+
pub(crate) fn new(trace: super::task::trace::Trace) -> Self {
51+
Self {
52+
trace: Trace { inner: trace },
53+
}
54+
}
55+
56+
/// A trace of this task's state.
57+
pub fn trace(&self) -> &Trace {
58+
&self.trace
59+
}
60+
}
61+
62+
impl fmt::Display for Trace {
63+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64+
self.inner.fmt(f)
65+
}
66+
}

0 commit comments

Comments
 (0)