mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
time: avoid interval deadline overflow (#8385)
This commit is contained in:
+49
-19
@@ -330,28 +330,34 @@ pub enum MissedTickBehavior {
|
||||
Skip,
|
||||
}
|
||||
|
||||
fn saturating_add(instant: Instant, duration: Duration) -> Instant {
|
||||
instant
|
||||
.checked_add(duration)
|
||||
.unwrap_or_else(Instant::far_future)
|
||||
}
|
||||
|
||||
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 => saturating_add(timeout, period),
|
||||
Self::Delay => saturating_add(now, period),
|
||||
Self::Skip => {
|
||||
now + period
|
||||
- Duration::from_nanos(
|
||||
((now - timeout).as_nanos() % period.as_nanos())
|
||||
.try_into()
|
||||
// This operation is practically guaranteed not to
|
||||
// fail, as in order for it to fail, `period` would
|
||||
// have to be longer than `now - timeout`, and both
|
||||
// would have to be longer than 584 years.
|
||||
//
|
||||
// If it did fail, there's not a good way to pass
|
||||
// the error along to the user, so we just panic.
|
||||
.expect(
|
||||
"too much time has elapsed since the interval was supposed to tick",
|
||||
),
|
||||
)
|
||||
let offset = Duration::from_nanos(
|
||||
((now - timeout).as_nanos() % period.as_nanos())
|
||||
.try_into()
|
||||
// This operation is practically guaranteed not to
|
||||
// fail, as in order for it to fail, `period` would
|
||||
// have to be longer than `now - timeout`, and both
|
||||
// would have to be longer than 584 years.
|
||||
//
|
||||
// If it did fail, there's not a good way to pass
|
||||
// the error along to the user, so we just panic.
|
||||
.expect(
|
||||
"too much time has elapsed since the interval was supposed to tick",
|
||||
),
|
||||
);
|
||||
saturating_add(now, period - offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,7 +523,9 @@ impl Interval {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn reset(&mut self) {
|
||||
self.delay.as_mut().reset(Instant::now() + self.period);
|
||||
self.delay
|
||||
.as_mut()
|
||||
.reset(saturating_add(Instant::now(), self.period));
|
||||
}
|
||||
|
||||
/// Resets the interval immediately.
|
||||
@@ -582,7 +590,9 @@ impl Interval {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn reset_after(&mut self, after: Duration) {
|
||||
self.delay.as_mut().reset(Instant::now() + after);
|
||||
self.delay
|
||||
.as_mut()
|
||||
.reset(saturating_add(Instant::now(), after));
|
||||
}
|
||||
|
||||
/// Resets the interval to a [`crate::time::Instant`] deadline.
|
||||
@@ -636,3 +646,23 @@ impl Interval {
|
||||
self.period
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn missed_tick_behavior_doesnt_panic_on_overflow() {
|
||||
let now = Instant::now();
|
||||
let timeout = now - Duration::from_millis(10);
|
||||
|
||||
for behavior in [
|
||||
MissedTickBehavior::Burst,
|
||||
MissedTickBehavior::Delay,
|
||||
MissedTickBehavior::Skip,
|
||||
] {
|
||||
let next = behavior.next_timeout(timeout, now, Duration::MAX);
|
||||
assert!(next > now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,18 @@ async fn skip() {
|
||||
check_interval_poll!(i, start, 1800);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reset_doesnt_panic_max_duration() {
|
||||
let mut interval = time::interval(Duration::MAX);
|
||||
interval.reset();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reset_after_doesnt_panic_max_duration() {
|
||||
let mut interval = time::interval(ms(1));
|
||||
interval.reset_after(Duration::MAX);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn reset() {
|
||||
let start = Instant::now();
|
||||
|
||||
Reference in New Issue
Block a user