Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement task dumps for current-thread runtime. #5608

Merged
merged 39 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1222dfb
tokio: introduce `Handle::dump` and impl for current-thread runtime
jswrenn Mar 29, 2023
d9aa093
tokio: limit taskdump test to tokio_unstable & 'taskdump' feature
jswrenn Apr 7, 2023
66cd9c9
tokio: limit taskdump example to 'taskdump' feature
jswrenn Apr 7, 2023
206b3d4
tokio: don't use let-else syntax
jswrenn Apr 11, 2023
b998f74
tokio: encapsulate taskdumping refcount shenanigans
jswrenn Apr 11, 2023
8a727a1
tokio: revise taskdump imports to conform to tokio conventions
jswrenn Apr 11, 2023
80dbaf7
tokio: fix formatting issue missed by rustfmt
jswrenn Apr 11, 2023
c132b33
tokio: limit taskdumps to linux
jswrenn Apr 11, 2023
9c86604
tokio: remove pointless let binding
jswrenn Apr 11, 2023
306a547
tokio: add taskdump roots to all appropriate places
jswrenn Apr 11, 2023
725bf3f
tokio: fix broken taskdump test
jswrenn Apr 11, 2023
c20af63
tokio: fallback taskdump example when unsupported
jswrenn Apr 11, 2023
52c8ca9
tokio: bump `backtrace-rs` version to 0.3.58
jswrenn Apr 11, 2023
fdc01e1
tokio: tweak dump example to avoid warnings
jswrenn Apr 11, 2023
144f378
tokio: make `taskdump` feature depend on `rt`
jswrenn Apr 11, 2023
c824389
tokio: limit taskdumps to specific, blessed platforms
jswrenn Apr 12, 2023
0c655bb
tokio: compile_error when `taskdump` feature is unsupported
jswrenn Apr 13, 2023
578c3c4
tokio: taskdumps cargo feature -> `--cfg tokio_taskump`
jswrenn Apr 13, 2023
ac7fa6b
tokio: restrict visibility of various items
jswrenn Apr 13, 2023
a246b11
tokio: rustfmt
jswrenn Apr 13, 2023
a62f9ee
tokio: don't cross-test with `--cfg tokio_taskdump`
jswrenn Apr 13, 2023
f695499
tokio: don't `--cfg tokio_taskdump` on unsupported platforms
jswrenn Apr 14, 2023
bb6990f
tokio: restrict visibility of `RawTask`
jswrenn Apr 14, 2023
51d15f0
tokio: don't alias `HashMap` and `HashSet`
jswrenn Apr 14, 2023
178bf74
Merge branch 'master' into taskdump-backtracing
jswrenn Apr 14, 2023
0a8d240
Merge branch 'master' into taskdump-backtracing
jswrenn Apr 16, 2023
b6d093a
tokio: tweak taskdump CI
jswrenn Apr 17, 2023
1ff355b
tokio: fix taskdump arch cfg
jswrenn Apr 17, 2023
9e0dd91
tokio: `--cfg tokio_taskdump` requires `rt` feature
jswrenn Apr 17, 2023
9a5e7fa
Merge branch 'master' into taskdump-backtracing
jswrenn Apr 17, 2023
f536db5
tokio: optimize LinkedList::for_each
jswrenn Apr 18, 2023
d3cda94
Merge branch 'master' into taskdump-backtracing
jswrenn Apr 18, 2023
a29974d
tokio: compile_error liberally for unsupported taskdump platforms
jswrenn Apr 19, 2023
51f970f
tokio: use `clear()` instead of `drain(..)` to drain queues
jswrenn Apr 19, 2023
8a8dceb
ci: don't --cfg tokio_taskdump in 'build loom tests'
jswrenn Apr 19, 2023
b0b1160
ci: test unstable with and without taskdumps
jswrenn Apr 19, 2023
8318d93
tokio: don't error on `all(not(feature = "rt"), tokio_taskdump))`
jswrenn Apr 19, 2023
0dc4b68
ci: test feature powerset with `unstable && !taskdump` cfgs
jswrenn Apr 26, 2023
04bd5e1
Merge branch 'master' into taskdump-backtracing
jswrenn Apr 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions .github/workflows/ci.yml
jswrenn marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,11 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- windows-latest
- ubuntu-latest
- macos-latest
include:
- os: windows-latest
- os: ubuntu-latest
rustflags: --cfg tokio_taskdump
- os: macos-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
Expand All @@ -202,10 +203,10 @@ jobs:
run: cargo test --all-features
working-directory: tokio
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
RUSTFLAGS: --cfg tokio_unstable ${{ matrix.rustflags }} -Dwarnings
# in order to run doctests for unstable features, we must also pass
# the unstable cfg to RustDoc
RUSTDOCFLAGS: --cfg tokio_unstable
RUSTDOCFLAGS: --cfg tokio_unstable ${{ matrix.rustflags }}

miri:
name: miri
Expand Down Expand Up @@ -299,9 +300,11 @@ jobs:
matrix:
include:
- target: i686-unknown-linux-gnu
rustflags: --cfg tokio_taskdump
- target: arm-unknown-linux-gnueabihf
- target: armv7-unknown-linux-gnueabihf
- target: aarch64-unknown-linux-gnu
rustflags: --cfg tokio_taskdump

# Run a platform without AtomicU64 and no const Mutex::new
- target: arm-unknown-linux-gnueabihf
Expand Down Expand Up @@ -347,15 +350,15 @@ jobs:
target: i686-unknown-linux-gnu
- run: cargo test -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64
# https://github.com/tokio-rs/tokio/pull/5356
# https://github.com/tokio-rs/tokio/issues/5373
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64

features:
name: features
Expand All @@ -377,7 +380,7 @@ jobs:
- name: check --feature-powerset --unstable
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is the test designed to test various combinations of flags, I suggest we also try with just --cfg tokio_unstable.

      # Try with unstable feature flags
      - name: check --feature-powerset --unstable
        run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
        env:
          RUSTFLAGS: --cfg tokio_unstable -Dwarnings
      # Try with unstable and taskdump feature flags
      - name: check --feature-powerset --unstable --taskdump
        run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
        env:
          RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings


minrust:
name: minrust
Expand Down Expand Up @@ -430,7 +433,7 @@ jobs:
cargo hack check --all-features --ignore-private
- name: "check --all-features --unstable -Z minimal-versions"
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
run: |
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
# from determining minimal versions based on dev-dependencies.
Expand Down Expand Up @@ -487,8 +490,8 @@ jobs:
- name: "doc --lib --all-features"
run: cargo doc --lib --no-deps --all-features --document-private-items
env:
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings

loom-compile:
name: build loom tests
Expand All @@ -505,7 +508,7 @@ jobs:
run: cargo test --no-run --lib --features full
working-directory: tokio
env:
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
RUSTFLAGS: --cfg loom --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
jswrenn marked this conversation as resolved.
Show resolved Hide resolved

check-readme:
name: Check README
Expand Down
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@ path = "named-pipe-ready.rs"
[[example]]
name = "named-pipe-multi-client"
path = "named-pipe-multi-client.rs"

[[example]]
name = "dump"
path = "dump.rs"
50 changes: 50 additions & 0 deletions examples/dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! This example demonstrates tokio's experimental taskdumping functionality.

#[cfg(all(
tokio_unstable,
tokio_taskdump,
target_os = "linux",
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
))]
#[tokio::main(flavor = "current_thread")]
async fn main() {
use std::hint::black_box;

#[inline(never)]
async fn a() {
black_box(b()).await
}

#[inline(never)]
async fn b() {
black_box(c()).await
}

#[inline(never)]
async fn c() {
black_box(tokio::task::yield_now()).await
}

tokio::spawn(a());
tokio::spawn(b());
tokio::spawn(c());

let handle = tokio::runtime::Handle::current();
let dump = handle.dump();

for (i, task) in dump.tasks().iter().enumerate() {
let trace = task.trace();
println!("task {i} trace:");
println!("{trace}");
}
}

#[cfg(not(all(
tokio_unstable,
tokio_taskdump,
target_os = "linux",
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
)))]
fn main() {
println!("task dumps are not available")
}
5 changes: 5 additions & 0 deletions tokio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ socket2 = { version = "0.4.9", optional = true, features = [ "all" ] }
[target.'cfg(tokio_unstable)'.dependencies]
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true } # Not in full

# Currently unstable. The API exposed by these features may be broken at any time.
# Requires `--cfg tokio_unstable` to enable.
[target.'cfg(tokio_taskdump)'.dependencies]
backtrace = { version = "0.3.58" }

[target.'cfg(unix)'.dependencies]
libc = { version = "0.2.42", optional = true }
signal-hook-registry = { version = "1.1.1", optional = true }
Expand Down
17 changes: 17 additions & 0 deletions tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,23 @@ compile_error!("Tokio's build script has incorrectly detected wasm.");
))]
compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm.");

#[cfg(all(not(tokio_unstable), tokio_taskdump))]
compile_error!("The `tokio_taskdump` feature requires `--cfg tokio_unstable`.");

#[cfg(all(
tokio_unstable,
tokio_taskdump,
feature = "rt",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would trigger this compile error even without rt. You aren't going to be enabling --cfg tokio_taskdump without knowing about it. Possibly also compile-fail on --cfg tokio_taskdump without rt.

Copy link
Contributor Author

@jswrenn jswrenn Apr 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly also compile-fail on --cfg tokio_taskdump without rt.

Hm, unfortunately, this causes both a CI failure an a usability issue: You might just keep --cfg tokio_unstable --cfg tokio_taskdump in your environment (.cargo/config makes this easy to do), but not always want to actually use the taskdumping feature. In such scenarios, your build will fail because of the explicit compile_error!.

Consequently, I've reverted my attempt at adding this compile_error: 8318d93

not(all(
target_os = "linux",
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
))
))]
compile_error!(
"The `tokio_taskdump` feature is only currently supported on \
linux, on `aarch64`, `x86` and `x86_64`."
);

// Includes re-exports used by macros.
//
// This module is not intended to be part of the public API. In general, any
Expand Down
30 changes: 30 additions & 0 deletions tokio/src/macros/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,36 @@ macro_rules! cfg_not_rt_multi_thread {
}
}

macro_rules! cfg_taskdump {
($($item:item)*) => {
$(
#[cfg(all(
tokio_unstable,
tokio_taskdump,
feature = "rt",
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64"
)
))]
#[cfg_attr(docsrs, doc(cfg(all(
tokio_unstable,
tokio_taskdump,
feature = "rt",
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64"
)
))))]
$item
)*
};
}

macro_rules! cfg_test_util {
($($item:item)*) => {
$(
Expand Down
34 changes: 34 additions & 0 deletions tokio/src/runtime/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ cfg_rt! {
use std::cell::RefCell;
use std::marker::PhantomData;
use std::time::Duration;

cfg_taskdump! {
use crate::runtime::task::trace;
}
}

struct Context {
Expand Down Expand Up @@ -45,6 +49,15 @@ struct Context {
/// Tracks the amount of "work" a task may still do before yielding back to
/// the sheduler
budget: Cell<coop::Budget>,

#[cfg(all(
tokio_unstable,
tokio_taskdump,
feature = "rt",
target_os = "linux",
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
))]
trace: trace::Context,
}

tokio_thread_local! {
Expand Down Expand Up @@ -75,6 +88,19 @@ tokio_thread_local! {
rng: FastRand::new(RngSeed::new()),

budget: Cell::new(coop::Budget::unconstrained()),

#[cfg(all(
tokio_unstable,
tokio_taskdump,
feature = "rt",
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64"
)
))]
trace: trace::Context::new(),
}
}
}
Expand Down Expand Up @@ -378,6 +404,14 @@ cfg_rt! {
matches!(self, EnterRuntime::Entered { .. })
}
}

cfg_taskdump! {
/// SAFETY: Callers of this function must ensure that trace frames always
/// form a valid linked list.
pub(crate) unsafe fn with_trace<R>(f: impl FnOnce(&trace::Context) -> R) -> R {
CONTEXT.with(|c| f(&c.trace))
}
}
}

// Forces the current "entered" state to be cleared while the closure
Expand Down
66 changes: 66 additions & 0 deletions tokio/src/runtime/dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! Snapshots of runtime state.

use std::fmt;

/// A snapshot of a runtime's state.
#[derive(Debug)]
pub struct Dump {
tasks: Tasks,
}

/// Snapshots of tasks.
#[derive(Debug)]
pub struct Tasks {
tasks: Vec<Task>,
}

/// A snapshot of a task.
#[derive(Debug)]
pub struct Task {
trace: Trace,
}

/// An execution trace of a task's last poll.
#[derive(Debug)]
pub struct Trace {
inner: super::task::trace::Trace,
}

impl Dump {
pub(crate) fn new(tasks: Vec<Task>) -> Self {
Self {
tasks: Tasks { tasks },
}
}

/// Tasks in this snapshot.
pub fn tasks(&self) -> &Tasks {
&self.tasks
}
}

impl Tasks {
/// Iterate over tasks.
pub fn iter(&self) -> impl Iterator<Item = &Task> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possible improvement here is to use impl Trait to simplify the definition of methods that return iterators. For example:

Suggested change
pub fn iter(&self) -> impl Iterator<Item = &Task> {
pub fn iter(&self) -> impl Iterator<Item = &Task> + '_ {

'_ means that the lifetime of the returned iterator is bound to the lifetime of the Tasks object

self.tasks.iter()
}
}

impl Task {
pub(crate) fn new(trace: super::task::trace::Trace) -> Self {
Self {
trace: Trace { inner: trace },
}
}

/// A trace of this task's state.
pub fn trace(&self) -> &Trace {
&self.trace
}
}

impl fmt::Display for Trace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
Loading