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
- Reduce per-tick clock reads by caching `get_time_ns()` results per event batch instead of calling it per field
- Use the real system clock (time is derived from the OS) rather than the incrementing test-clock counter for very long runs
- Restart the process/clock before the counter can reach u64::MAX (about 584 years of nanoseconds, so practically only reachable in test-clock mode)
- 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
- Cache clock reads per batch instead of calling get_time_ns per field
- Keep per-call increments (test-clock mode) out of hot loops
- Never use AtomicTime as a general-purpose counter
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
- Book snapshot timer start exceeds UnixNanos range
- Event '{}' should have associated handler
- DurationNanos overflow in from_millis
- DurationNanos overflow in from_secs
- DurationNanos overflow in from_mins
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/9bf6806ffbce1e6e.
Report an issue: GitHub.