{"record":{"id":"ed2915bb16133d00","repo":"clockworklabs/SpacetimeDB","slug":"duration-overflows-i64-microseconds","errorCode":null,"errorMessage":"Duration overflows i64 microseconds","messagePattern":"Duration overflows i64 microseconds","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/sats/src/time_duration.rs","lineNumber":78,"sourceCode":"    /// Converts `self` to `Duration`, clamping to 0 if negative.\n    pub fn to_duration_saturating(self) -> Duration {\n        self.to_duration().unwrap_or(Duration::ZERO)\n    }\n\n    /// Returns a positive `TimeDuration` with the magnitude of `self`.\n    pub fn abs(self) -> Self {\n        Self::from_micros(self.to_micros().saturating_abs())\n    }\n\n    /// Return a [`TimeDuration`] which represents the same span as `duration`.\n    ///\n    /// Panics if `duration.as_micros` overflows an `i64`\n    pub fn from_duration(duration: Duration) -> Self {\n        Self::from_micros(\n            duration\n                .as_micros()\n                .try_into()\n                .expect(\"Duration overflows i64 microseconds\"),\n        )\n    }\n\n    /// Returns `Some(self + other)`, or `None` if that value would be out of bounds for [`TimeDuration`].\n    pub fn checked_add(self, other: Self) -> Option<Self> {\n        self.to_micros().checked_add(other.to_micros()).map(Self::from_micros)\n    }\n\n    /// Returns `Some(self - other)`, or `None` if that value would be out of bounds for [`TimeDuration`].\n    pub fn checked_sub(self, other: Self) -> Option<Self> {\n        self.to_micros().checked_sub(other.to_micros()).map(Self::from_micros)\n    }\n\n    /// Generate an `iso8601` format string.\n    ///\n    /// This is the better supported format for use for the `pg wire protocol`.\n    ///\n    /// Example:","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/sats/src/time_duration.rs#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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`."],"exampleFix":"// before: unbounded backoff growth can overflow i64 micros\nlet delay = base * 2u32.pow(attempts as u32);\nlet td = TimeDuration::from_duration(delay);\n\n// after: saturate the computation\nlet delay = base.saturating_mul(2u32.saturating_pow(attempts as u32)).min(Duration::from_secs(86_400));\nlet td = TimeDuration::from_duration(delay);","handlingStrategy":"validation","validationCode":"const MAX_TD: Duration = Duration::from_secs(60 * 60 * 24 * 365 * 100_000);\nfn checked_from_duration(d: Duration) -> Result<TimeDuration, Overflow> {\n    (d <= MAX_TD).then(|| TimeDuration::from_duration(d)).ok_or(Overflow)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["rust","spacetimedb","duration","overflow","arithmetic","panic"],"backgroundTag":"arithmetic-overflow","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}