nautechsystems/nautilus_trader · critical

AtomicTime overflow: reached u64::MAX

Error message

AtomicTime overflow: reached u64::MAX

What it means

AtomicTime::time_since_epoch increments a stored u64 nanosecond timestamp on every call and refuses to wrap past u64::MAX. `checked_add(1)` returns None at the maximum value, so the `.expect` panics. This is a deliberate fatal-error guard: wrapping a monotonic clock would silently produce times in the past.

Source

Thrown at crates/core/src/time.rs:337

    ///
    /// # Panics
    ///
    /// Panics if the internal counter has reached `u64::MAX`, which would indicate the process has
    /// been running for longer than the representable range (~584 years) *or* the clock was
    /// manually corrupted.
    pub fn time_since_epoch(&self) -> UnixNanos {
        // This method guarantees strict consistency but may incur a performance cost under
        // high contention due to retries in the `compare_exchange` loop.
        let now = nanos_since_unix_epoch();

        loop {
            // Acquire to observe the latest stored value
            let last = self.timestamp_ns.load(Ordering::Acquire);

            // Ensure we never wrap past u64::MAX - treat that as a fatal error
            let incremented = last
                .checked_add(1)
                .expect("AtomicTime overflow: reached u64::MAX");
            let next = now.max(incremented);

            // AcqRel on success ensures this new value is published,
            // Acquire on failure reloads if we lost a CAS race.
            //
            // Note that under heavy contention (many threads calling this in tight loops),
            // the CAS loop may increase latency.
            //
            // However, in practice, the loop terminates quickly because:
            // - System time naturally advances between iterations
            // - Each iteration increments time by at least 1ns, preventing ABA problems
            // - True contention requiring retry is rare in normal usage patterns
            //
            // The concurrent stress test (4 threads × 100k iterations) validates this approach.
            if self
                .timestamp_ns
                .compare_exchange(last, next, Ordering::AcqRel, Ordering::Acquire)
                .is_ok()

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Reduce per-tick clock reads by caching `get_time_ns()` results per event batch instead of calling it per field
  2. Use the real system clock (time is derived from the OS) rather than the incrementing test-clock counter for very long runs
  3. Restart the process/clock before the counter can reach u64::MAX (about 584 years of nanoseconds, so practically only reachable in test-clock mode)
  4. If hit in tests, restructure the test to not drive AtomicTime to saturation

Example fix

// before: per-field clock reads in a hot loop
for tick in ticks {
    let ts = clock.get_time_ns(); // bumps AtomicTime each call
    process(tick, ts);
}
// after: one read per batch
let ts = clock.get_time_ns();
for tick in ticks {
    process(tick, ts);
}
Defensive patterns

Strategy: validation

Validate before calling

// Rust: before long test-clock loops, bound the number of clock reads
const MAX_CLOCK_READS: u128 = (u64::MAX as u128) - 1_000_000;
assert!(clock_reads_estimate < MAX_CLOCK_READS, "would exhaust AtomicTime counter");

Try / catch

// These are panics, not Results; only recoverable with catch_unwind
let result = std::panic::catch_unwind(|| clock.get_time_ns());

Prevention

When it happens

Trigger: Calling `time_since_epoch()` when the internal AtomicTime counter already holds u64::MAX. In practice this only happens under the `NautilusClock`/test-clock mode where every clock read bumps the counter: billions of calls to `get_time_ns` in one process, or a unit test deliberately driving the counter to overflow.

Common situations: Long-running simulations or backtests with a test clock that call the clock in a tight loop for months of simulated nanoseconds; fuzz tests; accidental use of AtomicTime as a general-purpose counter.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/9bf6806ffbce1e6e. Report an issue: GitHub.