clockworklabs/SpacetimeDB · error
Duration overflows i64 microseconds
Error message
Duration overflows i64 microseconds
What it means
Panic in `TimeDuration::from_duration`: converting a `std::time::Duration` to SpacetimeDB's microsecond-resolution `TimeDuration` requires `duration.as_micros()` to fit in an `i64` (max ≈ 292,471 years). The `try_into().expect("Duration overflows i64 microseconds")` panics for larger spans. The doc comment explicitly documents this as a panic condition.
Solutions
- Find the caller: debug-panic or log `duration` at the call site — the value will be astronomically large, revealing the arithmetic bug.
- Use `Duration::saturating_mul`/`saturating_add` (or `checked_*` + fallback) when computing durations from exponents or sums.
- If input can be arbitrary (config files, user strings), clamp to a sane maximum (e.g. 24h/365d) before conversion.
- Add unit tests with extreme inputs for duration math paths that feed `TimeDuration`.
Example fix
// before: unbounded backoff growth can overflow i64 micros let delay = base * 2u32.pow(attempts as u32); let td = TimeDuration::from_duration(delay); // after: saturate the computation let delay = base.saturating_mul(2u32.saturating_pow(attempts as u32)).min(Duration::from_secs(86_400)); let td = TimeDuration::from_duration(delay);
Defensive patterns
Strategy: validation
Validate before calling
const MAX_TD: Duration = Duration::from_secs(60 * 60 * 24 * 365 * 100_000);
fn checked_from_duration(d: Duration) -> Result<TimeDuration, Overflow> {
(d <= MAX_TD).then(|| TimeDuration::from_duration(d)).ok_or(Overflow)
} Prevention
- Use saturating_mul/saturating_add for any Duration math fed into TimeDuration.
- Clamp config/user-supplied durations to a sane maximum at parse time.
- Unit-test backoff/accumulator math with extreme inputs.
When it happens
Trigger: Calling `TimeDuration::from_duration(d)` (or APIs that route through it) with a Duration produced by overflowing arithmetic — e.g. multiplying durations, `Duration::MAX` scaling, or a Duration built from summed intervals in timers/schedulers. Realistic wall-clock durations never exceed ~292k years; this is almost always a computation bug or unvalidated external input.
Common situations: Retry/backoff calculators multiplying delays (e.g. `base * 2^64` without saturation); parsing user/config-supplied time strings with absurd units (typos like '9999999 days'); accumulator loops summing durations without bounds; fuzzing with arbitrary Duration inputs.
Related errors
- Duration since Unix epoch overflows i64 microseconds
- Timestamp with i64 microseconds before Unix epoch overflows…
- Timestamp with i64 microseconds since Unix epoch overflows…
- error syncing data to disk
- expected ModuleDef to contain key, but it does not
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/ed2915bb16133d00.
Report an issue: GitHub.
Appendix: source
Thrown at crates/sats/src/time_duration.rs:78
/// Converts `self` to `Duration`, clamping to 0 if negative.
pub fn to_duration_saturating(self) -> Duration {
self.to_duration().unwrap_or(Duration::ZERO)
}
/// Returns a positive `TimeDuration` with the magnitude of `self`.
pub fn abs(self) -> Self {
Self::from_micros(self.to_micros().saturating_abs())
}
/// Return a [`TimeDuration`] which represents the same span as `duration`.
///
/// Panics if `duration.as_micros` overflows an `i64`
pub fn from_duration(duration: Duration) -> Self {
Self::from_micros(
duration
.as_micros()
.try_into()
.expect("Duration overflows i64 microseconds"),
)
}
/// Returns `Some(self + other)`, or `None` if that value would be out of bounds for [`TimeDuration`].
pub fn checked_add(self, other: Self) -> Option<Self> {
self.to_micros().checked_add(other.to_micros()).map(Self::from_micros)
}
/// Returns `Some(self - other)`, or `None` if that value would be out of bounds for [`TimeDuration`].
pub fn checked_sub(self, other: Self) -> Option<Self> {
self.to_micros().checked_sub(other.to_micros()).map(Self::from_micros)
}
/// Generate an `iso8601` format string.
///
/// This is the better supported format for use for the `pg wire protocol`.
///
/// Example:View on GitHub (pinned to 6dee26c6ef)