Skip to content

Commit fede3db

Browse files
authored
tracing: replace future names with spawn locations in task spans (#3074)
## Motivation Currently, the per-task `tracing` spans generated by tokio's `tracing` feature flag include the `std::any::type_name` of the future that was spawned. When future combinators and/or libraries like Tower are in use, these future names can get _quite_ long. Furthermore, when formatting the `tracing` spans with their parent spans as context, any other task spans in the span context where the future was spawned from can _also_ include extremely long future names. In some cases, this can result in extremely high memory use just to store the future names. For example, in Linkerd, when we enable `tokio=trace` to enable the task spans, there's a spawned task whose future name is _232990 characters long_. A proxy with only 14 spawned tasks generates a task list that's over 690 KB. Enabling task spans under load results in the process getting OOM killed very quickly. ## Solution This branch removes future type names from the spans generated by `spawn`. As a replacement, to allow identifying which `spawn` call a span corresponds to, the task span now contains the source code location where `spawn` was called, when the compiler supports the `#[track_caller]` attribute. Since `track_caller` was stabilized in Rust 1.46.0, and our minimum supported Rust version is 1.45.0, we can't assume that `#[track_caller]` is always available. Instead, we have a RUSTFLAGS cfg, `tokio_track_caller`, that guards whether or not we use it. I've also added a `build.rs` that detects the compiler minor version, and sets the cfg flag automatically if the current compiler version is >= 1.46. This means users shouldn't have to enable `tokio_track_caller` manually. Here's the trace output from the `chat` example, before this change: ![Screenshot_20201030_110157](https://user-images.githubusercontent.com/2796466/97741071-6d408800-1a9f-11eb-9ed6-b25e72f58c7b.png) ...and after: ![Screenshot_20201030_110303](https://user-images.githubusercontent.com/2796466/97741112-7e899480-1a9f-11eb-9197-c5a3f9ea1c05.png) Closes #3073 Signed-off-by: Eliza Weisman <[email protected]>
1 parent 2b23aa7 commit fede3db

File tree

8 files changed

+63
-37
lines changed

8 files changed

+63
-37
lines changed

tokio/Cargo.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ mio = { version = "0.7.3", optional = true }
102102
num_cpus = { version = "1.8.0", optional = true }
103103
parking_lot = { version = "0.11.0", optional = true } # Not in full
104104
slab = { version = "0.4.1", optional = true }
105-
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # Not in full
105+
tracing = { version = "0.1.21", default-features = false, features = ["std"], optional = true } # Not in full
106106

107107
[target.'cfg(unix)'.dependencies]
108108
libc = { version = "0.2.42", optional = true }
@@ -126,9 +126,12 @@ tempfile = "3.1.0"
126126
[target.'cfg(loom)'.dev-dependencies]
127127
loom = { version = "0.3.5", features = ["futures", "checkpoint"] }
128128

129+
[build-dependencies]
130+
autocfg = "1" # Needed for conditionally enabling `track-caller`
131+
129132
[package.metadata.docs.rs]
130133
all-features = true
131134
rustdoc-args = ["--cfg", "docsrs"]
132135

133136
[package.metadata.playground]
134-
features = ["full"]
137+
features = ["full"]

tokio/build.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use autocfg::AutoCfg;
2+
3+
fn main() {
4+
match AutoCfg::new() {
5+
Ok(ac) => {
6+
// The #[track_caller] attribute was stabilized in rustc 1.46.0.
7+
if ac.probe_rustc_version(1, 46) {
8+
autocfg::emit("tokio_track_caller")
9+
}
10+
}
11+
12+
Err(e) => {
13+
// If we couldn't detect the compiler version and features, just
14+
// print a warning. This isn't a fatal error: we can still build
15+
// Tokio, we just can't enable cfgs automatically.
16+
println!(
17+
"cargo:warning=tokio: failed to detect compiler features: {}",
18+
e
19+
);
20+
}
21+
}
22+
}

tokio/src/runtime/handle.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,26 @@ impl Handle {
3939
// context::enter(self.clone(), f)
4040
// }
4141

42-
/// Run the provided function on an executor dedicated to blocking operations.
42+
/// Run the provided function on an executor dedicated to blocking
43+
/// operations.
44+
#[cfg_attr(tokio_track_caller, track_caller)]
4345
pub(crate) fn spawn_blocking<F, R>(&self, func: F) -> JoinHandle<R>
4446
where
4547
F: FnOnce() -> R + Send + 'static,
4648
{
4749
#[cfg(feature = "tracing")]
4850
let func = {
51+
#[cfg(tokio_track_caller)]
52+
let location = std::panic::Location::caller();
53+
#[cfg(tokio_track_caller)]
54+
let span = tracing::trace_span!(
55+
target: "tokio::task",
56+
"task",
57+
kind = %"blocking",
58+
function = %std::any::type_name::<F>(),
59+
spawn.location = %format_args!("{}:{}:{}", location.file(), location.line(), location.column()),
60+
);
61+
#[cfg(not(tokio_track_caller))]
4962
let span = tracing::trace_span!(
5063
target: "tokio::task",
5164
"task",

tokio/src/runtime/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,14 @@ cfg_rt! {
357357
/// });
358358
/// # }
359359
/// ```
360+
#[cfg_attr(tokio_track_caller, track_caller)]
360361
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
361362
where
362363
F: Future + Send + 'static,
363364
F::Output: Send + 'static,
364365
{
366+
#[cfg(feature = "tracing")]
367+
let future = crate::util::trace::task(future, "task");
365368
match &self.kind {
366369
#[cfg(feature = "rt-multi-thread")]
367370
Kind::ThreadPool(exec) => exec.spawn(future),
@@ -385,6 +388,7 @@ cfg_rt! {
385388
/// println!("now running on a worker thread");
386389
/// });
387390
/// # }
391+
#[cfg_attr(tokio_track_caller, track_caller)]
388392
pub fn spawn_blocking<F, R>(&self, func: F) -> JoinHandle<R>
389393
where
390394
F: FnOnce() -> R + Send + 'static,

tokio/src/task/blocking.rs

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ cfg_rt_multi_thread! {
104104
/// # Ok(())
105105
/// # }
106106
/// ```
107+
#[cfg_attr(tokio_track_caller, track_caller)]
107108
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
108109
where
109110
F: FnOnce() -> R + Send + 'static,

tokio/src/task/local.rs

+2
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ cfg_rt! {
190190
/// }).await;
191191
/// }
192192
/// ```
193+
#[cfg_attr(tokio_track_caller, track_caller)]
193194
pub fn spawn_local<F>(future: F) -> JoinHandle<F::Output>
194195
where
195196
F: Future + 'static,
@@ -273,6 +274,7 @@ impl LocalSet {
273274
/// }
274275
/// ```
275276
/// [`spawn_local`]: fn@spawn_local
277+
#[cfg_attr(tokio_track_caller, track_caller)]
276278
pub fn spawn_local<F>(&self, future: F) -> JoinHandle<F::Output>
277279
where
278280
F: Future + 'static,

tokio/src/task/spawn.rs

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ cfg_rt! {
122122
/// ```text
123123
/// error[E0391]: cycle detected when processing `main`
124124
/// ```
125+
#[cfg_attr(tokio_track_caller, track_caller)]
125126
pub fn spawn<T>(task: T) -> JoinHandle<T::Output>
126127
where
127128
T: Future + Send + 'static,

tokio/src/util/trace.rs

+14-34
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,27 @@
11
cfg_trace! {
22
cfg_rt! {
3-
use std::future::Future;
4-
use std::pin::Pin;
5-
use std::task::{Context, Poll};
6-
use pin_project_lite::pin_project;
7-
8-
use tracing::Span;
9-
10-
pin_project! {
11-
/// A future that has been instrumented with a `tracing` span.
12-
#[derive(Debug, Clone)]
13-
pub(crate) struct Instrumented<T> {
14-
#[pin]
15-
inner: T,
16-
span: Span,
17-
}
18-
}
19-
20-
impl<T: Future> Future for Instrumented<T> {
21-
type Output = T::Output;
22-
23-
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
24-
let this = self.project();
25-
let _enter = this.span.enter();
26-
this.inner.poll(cx)
27-
}
28-
}
29-
30-
impl<T> Instrumented<T> {
31-
pub(crate) fn new(inner: T, span: Span) -> Self {
32-
Self { inner, span }
33-
}
34-
}
3+
pub(crate) use tracing::instrument::Instrumented;
354

365
#[inline]
6+
#[cfg_attr(tokio_track_caller, track_caller)]
377
pub(crate) fn task<F>(task: F, kind: &'static str) -> Instrumented<F> {
8+
use tracing::instrument::Instrument;
9+
#[cfg(tokio_track_caller)]
10+
let location = std::panic::Location::caller();
11+
#[cfg(tokio_track_caller)]
12+
let span = tracing::trace_span!(
13+
target: "tokio::task",
14+
"task",
15+
%kind,
16+
spawn.location = %format_args!("{}:{}:{}", location.file(), location.line(), location.column()),
17+
);
18+
#[cfg(not(tokio_track_caller))]
3819
let span = tracing::trace_span!(
3920
target: "tokio::task",
4021
"task",
4122
%kind,
42-
future = %std::any::type_name::<F>(),
4323
);
44-
Instrumented::new(task, span)
24+
task.instrument(span)
4525
}
4626
}
4727
}

0 commit comments

Comments
 (0)