bevyengine/bevy · critical

overflow when adding duration to instant

Error message

overflow when adding duration to instant

What it means

bevy_platform's fallback `Instant` (no_std time) wraps a tick-count `Duration`. `impl Add<Duration> for Instant` uses `checked_add` and `.expect("overflow when adding duration to instant")` (crates/bevy_platform/src/time/fallback.rs:110), mirroring std's panic-on-overflow contract: if the resulting counter value cannot be represented, the add panics.

Source

Thrown at crates/bevy_platform/src/time/fallback.rs:110

    /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
    /// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
    /// otherwise.
    pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
        self.0.checked_sub(duration).map(Instant)
    }
}

impl Add<Duration> for Instant {
    type Output = Instant;

    /// # Panics
    ///
    /// This function may panic if the resulting point in time cannot be represented by the
    /// underlying data structure. See [`Instant::checked_add`] for a version without panic.
    fn add(self, other: Duration) -> Instant {
        self.checked_add(other)
            .expect("overflow when adding duration to instant")
    }
}

impl AddAssign<Duration> for Instant {
    fn add_assign(&mut self, other: Duration) {
        *self = *self + other;
    }
}

impl Sub<Duration> for Instant {
    type Output = Instant;

    fn sub(self, other: Duration) -> Instant {
        self.checked_sub(other)
            .expect("overflow when subtracting duration from instant")
    }
}

View on GitHub (pinned to 396ca72708)

Solutions

  1. Use `instant.checked_add(duration)` (or `saturating_add` where appropriate) instead of `+`
  2. Clamp durations from config/network before deadline math: `d.min(Duration::from_secs(86400))`
  3. Validate parsed timeout values at load time

Example fix

// before: panics if duration is huge
let deadline = start + timeout;

// after: handle the unrepresentable case
let deadline = start.checked_add(timeout).unwrap_or_else(|| start + Duration::from_secs(3600));
Defensive patterns

Strategy: validation

Validate before calling

let deadline = start
    .checked_add(timeout)
    .ok_or(TimeoutTooLarge)?; // or clamp:
// let deadline = start.checked_add(timeout).unwrap_or(start + Duration::from_secs(3600));

Prevention

When it happens

Trigger: `instant + duration` where the sum overflows the internal u64 nanosecond counter — practically this needs a Duration near `u64::MAX` nanoseconds (~584 years), e.g. `Instant::now() + Duration::MAX`, a `from_secs(u64::MAX)` from misparsed config, or saturated duration accumulation in a timer loop.

Common situations: Timeout/deadline math built from unchecked config values; `saturating_add`ed durations accumulating over long runs; unit tests using absurd durations against the fallback clock.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/c7e80ce066ec7ddb. Report an issue: GitHub.