From 16bc4e2e28798e6fe77efdcbbf5f29172810d526 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 20 Apr 2026 20:22:36 +0200 Subject: [PATCH] time: add `#[track_caller]` and panic docs to `timeout_at()` (#8077) --- tokio/src/time/timeout.rs | 25 ++++++++++++++++++++----- tokio/tests/time_panic.rs | 18 +++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/tokio/src/time/timeout.rs b/tokio/src/time/timeout.rs index ce4bf16d5..fc2065c11 100644 --- a/tokio/src/time/timeout.rs +++ b/tokio/src/time/timeout.rs @@ -142,16 +142,31 @@ where /// } /// # } /// ``` +/// +/// # Panics +/// +/// This function panics if there is no current timer set. +/// +/// It can be triggered when [`Builder::enable_time`] or +/// [`Builder::enable_all`] are not included in the builder. +/// +/// It can also panic whenever a timer is created outside of a +/// Tokio runtime. That is why `rt.block_on(sleep(...))` will panic, +/// since the function is executed outside of the runtime. +/// Whereas `rt.block_on(async {sleep(...).await})` doesn't panic. +/// And this is because wrapping the function on an async makes it lazy, +/// and so gets executed inside the runtime successfully without +/// panicking. +/// +/// [`Builder::enable_time`]: crate::runtime::Builder::enable_time +/// [`Builder::enable_all`]: crate::runtime::Builder::enable_all +#[track_caller] pub fn timeout_at(deadline: Instant, future: F) -> Timeout where F: IntoFuture, { let delay = sleep_until(deadline); - - Timeout { - value: future.into_future(), - delay, - } + Timeout::new_with_delay(future.into_future(), delay) } pin_project! { diff --git a/tokio/tests/time_panic.rs b/tokio/tests/time_panic.rs index aa7439cce..918d02a41 100644 --- a/tokio/tests/time_panic.rs +++ b/tokio/tests/time_panic.rs @@ -6,7 +6,7 @@ use futures::future; use std::error::Error; use std::time::Duration; use tokio::runtime::{Builder, Runtime}; -use tokio::time::{self, interval, interval_at, timeout, Instant}; +use tokio::time::{self, interval, interval_at, timeout, timeout_at, Instant}; mod support { pub mod panic; @@ -131,6 +131,22 @@ fn timeout_panic_caller() -> Result<(), Box> { Ok(()) } +#[test] +fn timeout_at_panic_caller() -> Result<(), Box> { + let panic_location_file = test_panic(|| { + // Runtime without `enable_time` so it has no current timer set. + let rt = Builder::new_current_thread().build().unwrap(); + rt.block_on(async { + let _timeout = timeout_at(Instant::now(), future::pending::<()>()); + }); + }); + + // The panic location should be in this file + assert_eq!(&panic_location_file.unwrap(), file!()); + + Ok(()) +} + fn current_thread() -> Runtime { tokio::runtime::Builder::new_current_thread() .enable_all()