Skip to content

Commit c82db01

Browse files
committed
Add track_caller to tokio core APIs (tokio-rs#4413)
Functions that may panic can be annotated with `#[track_caller]` so that in the event of a panic, the function where the user called the panicking function is shown instead of the file and line within Tokio source. This change adds track caller to all the non-unstable public APIs in Tokio core where the documentation describes how the function may panic due to incorrect context or inputs. Since each internal function needs to be annotated down to the actual panic, it makes sense to start in Tokio core functionality. Tests are needed to ensure that all the annotations remain in place in case internal refactoring occurs. The test installs a panic hook to extract the file location from the `PanicInfo` struct and clone it up to the outer scope to check that the panic was indeed reported from within the test file. The downside to this approach is that the panic hook is global while set and so we need a lot of extra functionality to effectively serialize the tests so that only a single panic can occur at a time. The annotation of `block_on` was removed as it did not work. It appears to be impossible to correctly chain track caller when the call stack to the panic passes through clojures, as the track caller annotation can only be applied to functions. Also, the panic message itself is very descriptive.
1 parent 90bc5fa commit c82db01

File tree

7 files changed

+115
-5
lines changed

7 files changed

+115
-5
lines changed

tokio/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ version = "0.3.6"
140140
tokio-test = { version = "0.4.0", path = "../tokio-test" }
141141
tokio-stream = { version = "0.1", path = "../tokio-stream" }
142142
futures = { version = "0.3.0", features = ["async-await"] }
143+
lazy_static = "1.4"
143144
mockall = "0.11.1"
144145
tempfile = "3.1.0"
145146
async-stream = "0.3"

tokio/src/runtime/builder.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ impl Builder {
200200
///
201201
/// The default value is the number of cores available to the system.
202202
///
203-
/// # Panic
203+
/// # Panics
204204
///
205205
/// When using the `current_thread` runtime this method will panic, since
206206
/// those variants do not allow setting worker thread counts.
@@ -237,9 +237,10 @@ impl Builder {
237237
/// rt.block_on(async move {});
238238
/// ```
239239
///
240-
/// # Panic
240+
/// # Panics
241241
///
242242
/// This will panic if `val` is not larger than `0`.
243+
#[track_caller]
243244
pub fn worker_threads(&mut self, val: usize) -> &mut Self {
244245
assert!(val > 0, "Worker threads cannot be set to 0");
245246
self.worker_threads = Some(val);
@@ -255,7 +256,7 @@ impl Builder {
255256
///
256257
/// The default value is 512.
257258
///
258-
/// # Panic
259+
/// # Panics
259260
///
260261
/// This will panic if `val` is not larger than `0`.
261262
///
@@ -267,6 +268,7 @@ impl Builder {
267268
/// [`spawn_blocking`]: fn@crate::task::spawn_blocking
268269
/// [`worker_threads`]: Self::worker_threads
269270
/// [`thread_keep_alive`]: Self::thread_keep_alive
271+
#[track_caller]
270272
#[cfg_attr(docsrs, doc(alias = "max_threads"))]
271273
pub fn max_blocking_threads(&mut self, val: usize) -> &mut Self {
272274
assert!(val > 0, "Max blocking threads cannot be set to 0");

tokio/src/runtime/context.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub(crate) fn try_current() -> Result<Handle, crate::runtime::TryCurrentError> {
1515
}
1616
}
1717

18+
#[track_caller]
1819
pub(crate) fn current() -> Handle {
1920
match try_current() {
2021
Ok(handle) => handle,

tokio/src/runtime/handle.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl Handle {
8787

8888
/// Returns a `Handle` view over the currently running `Runtime`.
8989
///
90-
/// # Panic
90+
/// # Panics
9191
///
9292
/// This will panic if called outside the context of a Tokio runtime. That means that you must
9393
/// call this on one of the threads **being run by the runtime**, or from a thread with an active
@@ -129,6 +129,7 @@ impl Handle {
129129
/// # });
130130
/// # }
131131
/// ```
132+
#[track_caller]
132133
pub fn current() -> Self {
133134
context::current()
134135
}

tokio/src/runtime/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,6 @@ cfg_rt! {
469469
/// ```
470470
///
471471
/// [handle]: fn@Handle::block_on
472-
#[track_caller]
473472
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
474473
#[cfg(all(tokio_unstable, feature = "tracing"))]
475474
let future = crate::util::trace::task(future, "block_on", None, task::Id::next().as_u64());

tokio/src/runtime/task/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl JoinError {
8282
/// }
8383
/// }
8484
/// ```
85+
#[track_caller]
8586
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
8687
self.try_into_panic()
8788
.expect("`JoinError` reason is not a panic.")

tokio/tests/rt_panic.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#![warn(rust_2018_idioms)]
2+
#![cfg(feature = "full")]
3+
4+
use lazy_static::lazy_static;
5+
use std::error::Error;
6+
use std::panic;
7+
use std::sync::{Arc, Mutex, Once};
8+
use tokio::runtime::{Builder, Handle, Runtime};
9+
10+
fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
11+
lazy_static! {
12+
static ref SET_UP_PANIC_HOOK: Once = Once::new();
13+
static ref PANIC_MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
14+
static ref PANIC_FILE: Mutex<String> = Mutex::new(String::new());
15+
}
16+
17+
SET_UP_PANIC_HOOK.call_once(|| {
18+
panic::set_hook(Box::new(|panic_info| {
19+
let panic_location = panic_info.location().unwrap();
20+
PANIC_FILE
21+
.lock()
22+
.unwrap()
23+
.clone_from(&panic_location.file().to_string());
24+
}));
25+
});
26+
27+
{
28+
let _guard = PANIC_MUTEX.lock();
29+
30+
let result = panic::catch_unwind(func);
31+
32+
if result.is_err() {
33+
Some(PANIC_FILE.lock().ok()?.clone())
34+
} else {
35+
None
36+
}
37+
}
38+
}
39+
40+
#[test]
41+
fn test_current_handle_panic_caller() -> Result<(), Box<dyn Error>> {
42+
let panic_location_file = test_panic(|| {
43+
let _ = Handle::current();
44+
});
45+
46+
// The panic location should be in this file
47+
assert_eq!(&panic_location_file.unwrap(), file!());
48+
49+
Ok(())
50+
}
51+
52+
#[test]
53+
fn test_into_panic_panic_caller() -> Result<(), Box<dyn Error>> {
54+
let panic_location_file = test_panic(move || {
55+
let rt = basic();
56+
rt.block_on(async {
57+
let handle = tokio::spawn(async {
58+
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
59+
});
60+
61+
handle.abort();
62+
63+
let err = handle.await.unwrap_err();
64+
assert!(!&err.is_panic());
65+
66+
let _ = err.into_panic();
67+
});
68+
});
69+
70+
// The panic location should be in this file
71+
assert_eq!(&panic_location_file.unwrap(), file!());
72+
73+
Ok(())
74+
}
75+
76+
#[test]
77+
fn test_builder_worker_threads_panic_caller() -> Result<(), Box<dyn Error>> {
78+
let panic_location_file = test_panic(|| {
79+
let _ = Builder::new_multi_thread().worker_threads(0).build();
80+
});
81+
82+
// The panic location should be in this file
83+
assert_eq!(&panic_location_file.unwrap(), file!());
84+
85+
Ok(())
86+
}
87+
88+
#[test]
89+
fn test_builder_max_blocking_threads_panic_caller() -> Result<(), Box<dyn Error>> {
90+
let panic_location_file = test_panic(|| {
91+
let _ = Builder::new_multi_thread().max_blocking_threads(0).build();
92+
});
93+
94+
// The panic location should be in this file
95+
assert_eq!(&panic_location_file.unwrap(), file!());
96+
97+
Ok(())
98+
}
99+
100+
fn basic() -> Runtime {
101+
tokio::runtime::Builder::new_current_thread()
102+
.enable_all()
103+
.build()
104+
.unwrap()
105+
}

0 commit comments

Comments
 (0)