Skip to content

Commit b51aa8f

Browse files
authored
io: add track_caller to public APIs (#4793)
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 io APIs in the main tokio crate where the documentation describes how the function may panic due to incorrect context or inputs. Additionally, the documentation for `AsyncFd` was updated to indicate that the functions `new` and `with_intent` can panic. Tests are included to cover each potentially panicking function. The logic to test the location of a panic (which is a little complex), has been moved to a test support module. Refs: #4413
1 parent 3cc6168 commit b51aa8f

File tree

8 files changed

+236
-66
lines changed

8 files changed

+236
-66
lines changed

tokio/src/io/async_fd.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,22 +167,34 @@ pub struct AsyncFdReadyMutGuard<'a, T: AsRawFd> {
167167
const ALL_INTEREST: Interest = Interest::READABLE.add(Interest::WRITABLE);
168168

169169
impl<T: AsRawFd> AsyncFd<T> {
170-
#[inline]
171170
/// Creates an AsyncFd backed by (and taking ownership of) an object
172171
/// implementing [`AsRawFd`]. The backing file descriptor is cached at the
173172
/// time of creation.
174173
///
175174
/// This method must be called in the context of a tokio runtime.
175+
///
176+
/// # Panics
177+
///
178+
/// This function panics if there is no current reactor set, or if the `rt`
179+
/// feature flag is not enabled.
180+
#[inline]
181+
#[track_caller]
176182
pub fn new(inner: T) -> io::Result<Self>
177183
where
178184
T: AsRawFd,
179185
{
180186
Self::with_interest(inner, ALL_INTEREST)
181187
}
182188

183-
#[inline]
184189
/// Creates new instance as `new` with additional ability to customize interest,
185190
/// allowing to specify whether file descriptor will be polled for read, write or both.
191+
///
192+
/// # Panics
193+
///
194+
/// This function panics if there is no current reactor set, or if the `rt`
195+
/// feature flag is not enabled.
196+
#[inline]
197+
#[track_caller]
186198
pub fn with_interest(inner: T, interest: Interest) -> io::Result<Self>
187199
where
188200
T: AsRawFd,

tokio/src/io/driver/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ cfg_rt! {
258258
///
259259
/// This function panics if there is no current reactor set and `rt` feature
260260
/// flag is not enabled.
261+
#[track_caller]
261262
pub(super) fn current() -> Self {
262263
crate::runtime::context::io_handle().expect("A Tokio 1.x context was found, but IO is disabled. Call `enable_io` on the runtime builder to enable IO.")
263264
}

tokio/src/io/read_buf.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ impl<'a> ReadBuf<'a> {
152152
///
153153
/// Panics if `self.remaining()` is less than `n`.
154154
#[inline]
155+
#[track_caller]
155156
pub fn initialize_unfilled_to(&mut self, n: usize) -> &mut [u8] {
156157
assert!(self.remaining() >= n, "n overflows remaining");
157158

@@ -195,6 +196,7 @@ impl<'a> ReadBuf<'a> {
195196
///
196197
/// Panics if the filled region of the buffer would become larger than the initialized region.
197198
#[inline]
199+
#[track_caller]
198200
pub fn advance(&mut self, n: usize) {
199201
let new = self.filled.checked_add(n).expect("filled overflow");
200202
self.set_filled(new);
@@ -211,6 +213,7 @@ impl<'a> ReadBuf<'a> {
211213
///
212214
/// Panics if the filled region of the buffer would become larger than the initialized region.
213215
#[inline]
216+
#[track_caller]
214217
pub fn set_filled(&mut self, n: usize) {
215218
assert!(
216219
n <= self.initialized,
@@ -241,6 +244,7 @@ impl<'a> ReadBuf<'a> {
241244
///
242245
/// Panics if `self.remaining()` is less than `buf.len()`.
243246
#[inline]
247+
#[track_caller]
244248
pub fn put_slice(&mut self, buf: &[u8]) {
245249
assert!(
246250
self.remaining() >= buf.len(),

tokio/src/io/split.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl<T> ReadHalf<T> {
7474
/// same `split` operation this method will panic.
7575
/// This can be checked ahead of time by comparing the stream ID
7676
/// of the two halves.
77+
#[track_caller]
7778
pub fn unsplit(self, wr: WriteHalf<T>) -> T {
7879
if self.is_pair_of(&wr) {
7980
drop(wr);

tokio/tests/io_panic.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#![warn(rust_2018_idioms)]
2+
#![cfg(feature = "full")]
3+
4+
use std::task::{Context, Poll};
5+
use std::{error::Error, pin::Pin};
6+
use tokio::io::{self, split, AsyncRead, AsyncWrite, ReadBuf};
7+
8+
mod support {
9+
pub mod panic;
10+
}
11+
use support::panic::test_panic;
12+
13+
struct RW;
14+
15+
impl AsyncRead for RW {
16+
fn poll_read(
17+
self: Pin<&mut Self>,
18+
_cx: &mut Context<'_>,
19+
buf: &mut ReadBuf<'_>,
20+
) -> Poll<io::Result<()>> {
21+
buf.put_slice(&[b'z']);
22+
Poll::Ready(Ok(()))
23+
}
24+
}
25+
26+
impl AsyncWrite for RW {
27+
fn poll_write(
28+
self: Pin<&mut Self>,
29+
_cx: &mut Context<'_>,
30+
_buf: &[u8],
31+
) -> Poll<Result<usize, io::Error>> {
32+
Poll::Ready(Ok(1))
33+
}
34+
35+
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
36+
Poll::Ready(Ok(()))
37+
}
38+
39+
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
40+
Poll::Ready(Ok(()))
41+
}
42+
}
43+
44+
#[cfg(unix)]
45+
mod unix {
46+
use std::os::unix::prelude::{AsRawFd, RawFd};
47+
48+
pub struct MockFd;
49+
50+
impl AsRawFd for MockFd {
51+
fn as_raw_fd(&self) -> RawFd {
52+
0
53+
}
54+
}
55+
}
56+
57+
#[test]
58+
fn read_buf_initialize_unfilled_to_panic_caller() -> Result<(), Box<dyn Error>> {
59+
let panic_location_file = test_panic(|| {
60+
let mut buffer = Vec::<u8>::new();
61+
let mut read_buf = ReadBuf::new(&mut buffer);
62+
63+
read_buf.initialize_unfilled_to(2);
64+
});
65+
66+
// The panic location should be in this file
67+
assert_eq!(&panic_location_file.unwrap(), file!());
68+
69+
Ok(())
70+
}
71+
72+
#[test]
73+
fn read_buf_advance_panic_caller() -> Result<(), Box<dyn Error>> {
74+
let panic_location_file = test_panic(|| {
75+
let mut buffer = Vec::<u8>::new();
76+
let mut read_buf = ReadBuf::new(&mut buffer);
77+
78+
read_buf.advance(2);
79+
});
80+
81+
// The panic location should be in this file
82+
assert_eq!(&panic_location_file.unwrap(), file!());
83+
84+
Ok(())
85+
}
86+
87+
#[test]
88+
fn read_buf_set_filled_panic_caller() -> Result<(), Box<dyn Error>> {
89+
let panic_location_file = test_panic(|| {
90+
let mut buffer = Vec::<u8>::new();
91+
let mut read_buf = ReadBuf::new(&mut buffer);
92+
93+
read_buf.set_filled(2);
94+
});
95+
96+
// The panic location should be in this file
97+
assert_eq!(&panic_location_file.unwrap(), file!());
98+
99+
Ok(())
100+
}
101+
102+
#[test]
103+
fn read_buf_put_slice_panic_caller() -> Result<(), Box<dyn Error>> {
104+
let panic_location_file = test_panic(|| {
105+
let mut buffer = Vec::<u8>::new();
106+
let mut read_buf = ReadBuf::new(&mut buffer);
107+
108+
let new_slice = [0x40_u8, 0x41_u8];
109+
110+
read_buf.put_slice(&new_slice);
111+
});
112+
113+
// The panic location should be in this file
114+
assert_eq!(&panic_location_file.unwrap(), file!());
115+
116+
Ok(())
117+
}
118+
119+
#[test]
120+
fn unsplit_panic_caller() -> Result<(), Box<dyn Error>> {
121+
let panic_location_file = test_panic(|| {
122+
let (r1, _w1) = split(RW);
123+
let (_r2, w2) = split(RW);
124+
r1.unsplit(w2);
125+
});
126+
127+
// The panic location should be in this file
128+
assert_eq!(&panic_location_file.unwrap(), file!());
129+
130+
Ok(())
131+
}
132+
133+
#[test]
134+
#[cfg(unix)]
135+
fn async_fd_new_panic_caller() -> Result<(), Box<dyn Error>> {
136+
use tokio::io::unix::AsyncFd;
137+
use tokio::runtime::Builder;
138+
139+
let panic_location_file = test_panic(|| {
140+
// Runtime without `enable_io` so it has no IO driver set.
141+
let rt = Builder::new_current_thread().build().unwrap();
142+
rt.block_on(async {
143+
let fd = unix::MockFd;
144+
145+
let _ = AsyncFd::new(fd);
146+
});
147+
});
148+
149+
// The panic location should be in this file
150+
assert_eq!(&panic_location_file.unwrap(), file!());
151+
152+
Ok(())
153+
}
154+
155+
#[test]
156+
#[cfg(unix)]
157+
fn async_fd_with_interest_panic_caller() -> Result<(), Box<dyn Error>> {
158+
use tokio::io::unix::AsyncFd;
159+
use tokio::io::Interest;
160+
use tokio::runtime::Builder;
161+
162+
let panic_location_file = test_panic(|| {
163+
// Runtime without `enable_io` so it has no IO driver set.
164+
let rt = Builder::new_current_thread().build().unwrap();
165+
rt.block_on(async {
166+
let fd = unix::MockFd;
167+
168+
let _ = AsyncFd::with_interest(fd, Interest::READABLE);
169+
});
170+
});
171+
172+
// The panic location should be in this file
173+
assert_eq!(&panic_location_file.unwrap(), file!());
174+
175+
Ok(())
176+
}

tokio/tests/rt_panic.rs

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,13 @@
22
#![cfg(feature = "full")]
33

44
use futures::future;
5-
use parking_lot::{const_mutex, Mutex};
65
use std::error::Error;
7-
use std::panic;
8-
use std::sync::Arc;
96
use tokio::runtime::{Builder, Handle, Runtime};
107

11-
fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
12-
static PANIC_MUTEX: Mutex<()> = const_mutex(());
13-
14-
{
15-
let _guard = PANIC_MUTEX.lock();
16-
let panic_file: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
17-
18-
let prev_hook = panic::take_hook();
19-
{
20-
let panic_file = panic_file.clone();
21-
panic::set_hook(Box::new(move |panic_info| {
22-
let panic_location = panic_info.location().unwrap();
23-
panic_file
24-
.lock()
25-
.clone_from(&Some(panic_location.file().to_string()));
26-
}));
27-
}
28-
29-
let result = panic::catch_unwind(func);
30-
// Return to the previously set panic hook (maybe default) so that we get nice error
31-
// messages in the tests.
32-
panic::set_hook(prev_hook);
33-
34-
if result.is_err() {
35-
panic_file.lock().clone()
36-
} else {
37-
None
38-
}
39-
}
8+
mod support {
9+
pub mod panic;
4010
}
11+
use support::panic::test_panic;
4112

4213
#[test]
4314
fn current_handle_panic_caller() -> Result<(), Box<dyn Error>> {

tokio/tests/support/panic.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use parking_lot::{const_mutex, Mutex};
2+
use std::panic;
3+
use std::sync::Arc;
4+
5+
pub fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
6+
static PANIC_MUTEX: Mutex<()> = const_mutex(());
7+
8+
{
9+
let _guard = PANIC_MUTEX.lock();
10+
let panic_file: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
11+
12+
let prev_hook = panic::take_hook();
13+
{
14+
let panic_file = panic_file.clone();
15+
panic::set_hook(Box::new(move |panic_info| {
16+
let panic_location = panic_info.location().unwrap();
17+
panic_file
18+
.lock()
19+
.clone_from(&Some(panic_location.file().to_string()));
20+
}));
21+
}
22+
23+
let result = panic::catch_unwind(func);
24+
// Return to the previously set panic hook (maybe default) so that we get nice error
25+
// messages in the tests.
26+
panic::set_hook(prev_hook);
27+
28+
if result.is_err() {
29+
panic_file.lock().clone()
30+
} else {
31+
None
32+
}
33+
}
34+
}

tokio/tests/time_panic.rs

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,15 @@
22
#![cfg(feature = "full")]
33

44
use futures::future;
5-
use parking_lot::{const_mutex, Mutex};
65
use std::error::Error;
7-
use std::panic;
8-
use std::sync::Arc;
96
use std::time::Duration;
107
use tokio::runtime::{Builder, Runtime};
118
use tokio::time::{self, interval, interval_at, timeout, Instant};
129

13-
fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
14-
static PANIC_MUTEX: Mutex<()> = const_mutex(());
15-
16-
{
17-
let _guard = PANIC_MUTEX.lock();
18-
let panic_file: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
19-
20-
let prev_hook = panic::take_hook();
21-
{
22-
let panic_file = panic_file.clone();
23-
panic::set_hook(Box::new(move |panic_info| {
24-
let panic_location = panic_info.location().unwrap();
25-
panic_file
26-
.lock()
27-
.clone_from(&Some(panic_location.file().to_string()));
28-
}));
29-
}
30-
31-
let result = panic::catch_unwind(func);
32-
// Return to the previously set panic hook (maybe default) so that we get nice error
33-
// messages in the tests.
34-
panic::set_hook(prev_hook);
35-
36-
if result.is_err() {
37-
panic_file.lock().clone()
38-
} else {
39-
None
40-
}
41-
}
10+
mod support {
11+
pub mod panic;
4212
}
13+
use support::panic::test_panic;
4314

4415
#[test]
4516
fn pause_panic_caller() -> Result<(), Box<dyn Error>> {

0 commit comments

Comments
 (0)