From 47ee3e131faaf6f2f7e3b0c4dd02c1e697bd4bc1 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 4 Mar 2025 13:06:17 +0200 Subject: [PATCH 1/4] tokio/interval: Fix overflow for interval tick Signed-off-by: Alexandru Vasile --- tokio/src/time/interval.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tokio/src/time/interval.rs b/tokio/src/time/interval.rs index 0153a567f10..055071b537f 100644 --- a/tokio/src/time/interval.rs +++ b/tokio/src/time/interval.rs @@ -475,7 +475,12 @@ 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 + > timeout + .checked_add(Duration::from_millis(5)) + .unwrap_or_else(Instant::far_future) + { self.missed_tick_behavior .next_timeout(timeout, now, self.period) } else { From 2340ec7f58fca05b70b434f76c9a5dffc5b0dd47 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 4 Mar 2025 13:39:25 +0200 Subject: [PATCH 2/4] tokio/interval: Use saturating duration instead Signed-off-by: Alexandru Vasile --- tokio/src/time/interval.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tokio/src/time/interval.rs b/tokio/src/time/interval.rs index 055071b537f..925833ff1e7 100644 --- a/tokio/src/time/interval.rs +++ b/tokio/src/time/interval.rs @@ -476,11 +476,7 @@ impl Interval { // schedule the next tick according to how the user specified with // `MissedTickBehavior` - let next = if now - > timeout - .checked_add(Duration::from_millis(5)) - .unwrap_or_else(Instant::far_future) - { + let next = if now.saturating_duration_since(timeout) > Duration::from_millis(5) { self.missed_tick_behavior .next_timeout(timeout, now, self.period) } else { From a11961ad5f8a1755dd3e99030516e7e9c23b546b Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 4 Mar 2025 14:16:35 +0200 Subject: [PATCH 3/4] tokio/interval: Fix overflows in MissedTickBehavior Signed-off-by: Alexandru Vasile --- tokio/src/time/interval.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tokio/src/time/interval.rs b/tokio/src/time/interval.rs index 925833ff1e7..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() From 2bcccc07f1dcb0e25964dd63f5d84ebbbde7dfe1 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 4 Mar 2025 14:24:15 +0200 Subject: [PATCH 4/4] tokio/tests: Adjust interval panic test Signed-off-by: Alexandru Vasile --- tokio/tests/time_interval.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) 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()); }