diff --git a/tokio/src/time/interval.rs b/tokio/src/time/interval.rs index 0153a567f10..2af1e49fbed 100644 --- a/tokio/src/time/interval.rs +++ b/tokio/src/time/interval.rs @@ -340,10 +340,12 @@ impl MissedTickBehavior { /// If a tick is missed, this method is called to determine when the next tick should happen. fn next_timeout(&self, timeout: Instant, now: Instant, period: Duration) -> Instant { match self { - Self::Burst => timeout + period, - Self::Delay => now + period, + Self::Burst => timeout + .checked_add(period) + .unwrap_or_else(Instant::far_future), + Self::Delay => now.checked_add(period).unwrap_or_else(Instant::far_future), Self::Skip => { - now + period + now.checked_add(period).unwrap_or_else(Instant::far_future) - Duration::from_nanos( ((now - timeout).as_nanos() % period.as_nanos()) .try_into() @@ -475,7 +477,8 @@ impl Interval { // However, if a tick took excessively long and we are now behind, // schedule the next tick according to how the user specified with // `MissedTickBehavior` - let next = if now > timeout + Duration::from_millis(5) { + + let next = if now.saturating_duration_since(timeout) > Duration::from_millis(5) { self.missed_tick_behavior .next_timeout(timeout, now, self.period) } else { diff --git a/tokio/tests/time_interval.rs b/tokio/tests/time_interval.rs index 7472a37123c..71a83567966 100644 --- a/tokio/tests/time_interval.rs +++ b/tokio/tests/time_interval.rs @@ -470,8 +470,25 @@ async fn stream_with_interval_poll_tick_no_waking() { assert_eq!(items, vec![]); } -#[tokio::test(start_paused = true)] +#[tokio::test] async fn interval_doesnt_panic_max_duration_when_polling() { - let mut timer = task::spawn(time::interval(Duration::MAX)); + let mut timer = task::spawn(time::interval(Duration::from_secs(u64::MAX))); + + // The `Interval::delay` is not ready yet. + let result = timer.enter(|cx, mut timer| timer.poll_tick(cx)); + assert!(result.is_pending()); + + // Sleep 1 second to ensure we capture a missed tick. + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + // Call the poll tick again to check the duration doesn't panic. + // These 2 polls are equivalent to `timer.tick().await` that disarm the first interval fire. assert_ready!(timer.enter(|cx, mut timer| timer.poll_tick(cx))); + + // Sleep again. + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + // This time we expect the timer to fire at the specified interval (ie too far in the future). + let result = timer.enter(|cx, mut timer| timer.poll_tick(cx)); + assert!(result.is_pending()); }