From b05009794d86cb404037c6d3b11477e724d0dde0 Mon Sep 17 00:00:00 2001 From: Stiopa Koltsov Date: Wed, 24 Feb 2021 19:23:05 +0000 Subject: [PATCH] Do not panic on timeout(Duration::MAX) It is tempting to use very large `Duration` value to get a practically infinite timeout. Before this commit tokio panics on checked Instant + Duration overflow. This commit implements very simple fix: if Instant + Duration overflows, we use duration = 100 years. Better fix should avoid firing a timer on duration overflow. It requires deeper understanding how timers work, but also it is not clear, for example, what `Sleep::deadline` function should return. Similar fix is done for `sleep`. --- tokio/src/time/driver/sleep.rs | 9 ++++++++- tokio/src/time/instant.rs | 7 +++++++ tokio/src/time/timeout.rs | 6 +++++- tokio/tests/time_timeout.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/tokio/src/time/driver/sleep.rs b/tokio/src/time/driver/sleep.rs index d8173c2524b..5c366293d5f 100644 --- a/tokio/src/time/driver/sleep.rs +++ b/tokio/src/time/driver/sleep.rs @@ -58,7 +58,10 @@ pub fn sleep_until(deadline: Instant) -> Sleep { // Alias for old name in 0.x #[cfg_attr(docsrs, doc(alias = "delay_for"))] pub fn sleep(duration: Duration) -> Sleep { - sleep_until(Instant::now() + duration) + match Instant::now().checked_add(duration) { + Some(deadline) => sleep_until(deadline), + None => sleep_until(Instant::far_future()), + } } pin_project! { @@ -168,6 +171,10 @@ impl Sleep { Sleep { deadline, entry } } + pub(crate) fn far_future() -> Sleep { + Self::new_timeout(Instant::far_future()) + } + /// Returns the instant at which the future will complete. pub fn deadline(&self) -> Instant { self.deadline diff --git a/tokio/src/time/instant.rs b/tokio/src/time/instant.rs index f4d6eacbfa5..de9f40d9e5a 100644 --- a/tokio/src/time/instant.rs +++ b/tokio/src/time/instant.rs @@ -54,6 +54,13 @@ impl Instant { Instant { std } } + pub(crate) fn far_future() -> Instant { + // Roughly 100 years from now. + // API does not provide a way to obtain max `Instant` + // or convert specific date in the future to instant. + Self::now() + Duration::from_secs(86400 * 365 * 100) + } + /// Convert the value into a `std::time::Instant`. pub fn into_std(self) -> std::time::Instant { self.std diff --git a/tokio/src/time/timeout.rs b/tokio/src/time/timeout.rs index 9d15a7205cd..61964ad2426 100644 --- a/tokio/src/time/timeout.rs +++ b/tokio/src/time/timeout.rs @@ -49,7 +49,11 @@ pub fn timeout(duration: Duration, future: T) -> Timeout where T: Future, { - let delay = Sleep::new_timeout(Instant::now() + duration); + let deadline = Instant::now().checked_add(duration); + let delay = match deadline { + Some(deadline) => Sleep::new_timeout(deadline), + None => Sleep::far_future(), + }; Timeout::new_with_delay(future, delay) } diff --git a/tokio/tests/time_timeout.rs b/tokio/tests/time_timeout.rs index 4efcd8ca82f..74ec3a3c881 100644 --- a/tokio/tests/time_timeout.rs +++ b/tokio/tests/time_timeout.rs @@ -74,6 +74,33 @@ async fn future_and_timeout_in_future() { assert_ready_ok!(fut.poll()).unwrap(); } +#[tokio::test] +async fn very_large_timeout() { + time::pause(); + + // Not yet complete + let (tx, rx) = oneshot::channel(); + + // copy-paste unstable `Duration::MAX` + let duration_max = Duration::from_secs(u64::MAX) + Duration::from_nanos(999_999_999); + + // Wrap it with a deadline + let mut fut = task::spawn(timeout(duration_max, rx)); + + // Ready! + assert_pending!(fut.poll()); + + // Turn the timer, it runs for the elapsed time + time::advance(Duration::from_secs(86400 * 365 * 90)).await; + + assert_pending!(fut.poll()); + + // Complete the future + tx.send(()).unwrap(); + + assert_ready_ok!(fut.poll()).unwrap(); +} + #[tokio::test] async fn deadline_now_elapses() { use futures::future::pending;