nautechsystems/nautilus_trader · error

timer queue peeked Some but pop returned None

Error message

timer queue peeked Some but pop returned None

What it means

In LiveClock/advance_time, the timer queue is peeked and, if an entry is due, immediately popped. The expect fires if the BinaryHeap yields None on pop right after a successful peek — an internal invariant violation that should be impossible in single-threaded code (duplicate peek/pop race or corrupted queue).

Source

Thrown at crates/common/src/clock.rs:959

            to_time_ns >= from_time_ns,
            "Invariant: time must be non-decreasing, `to_time_ns` {to_time_ns} < `from_time_ns` {from_time_ns}"
        );

        if set_time {
            self.time.set_time(to_time_ns);
        }

        let mut events: Vec<TimeEvent> = Vec::new();

        while self
            .timer_queue
            .peek()
            .is_some_and(|entry| entry.0.ts_event <= to_time_ns)
        {
            let entry = self
                .timer_queue
                .pop()
                .expect("timer queue peeked Some but pop returned None");

            let Some((event, next_event)) = self.advance_timer_from_entry(&entry.0) else {
                continue;
            };

            events.push(event);
            if let Some(next_event) = next_event {
                self.timer_queue.push(next_event);
            }
        }

        self.compact_timer_queue_if_needed();

        if events.len() >= WARN_TIME_EVENTS_THRESHOLD {
            log::warn!(
                "Allocated {} time events during clock advancement from {} to {}, \
                 consider stopping the timer between large time ranges with no data points",
                events.len().separate_with_commas(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure no other thread/task mutates the timer queue while advance_time runs
  2. If a custom BinaryHeap/wrapper is in play, fix its pop/peek implementation
  3. Check for concurrent calls to advance_time or timer cancellation during iteration
  4. Report as a bug if reproducible with stock components — it is an internal invariant violation
Defensive patterns

Strategy: retry

Try / catch

// Internal invariant: on panic, capture state and report
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| clock.advance_time(to_ns)));
if result.is_err() {
    eprintln!("timer queue invariant violated; rebuild timers");
}

Prevention

When it happens

Trigger: Calling advance_time (typically from tests that advance the TestClock/advance time past timer deadlines) where peek() reports a due entry (entry.0.ts_event <= to_time_ns) but pop() returns None.

Common situations: Custom timer queue implementations or heap wrappers with buggy behavior; concurrent mutation of the timer queue from another thread; test harnesses replacing the queue; heap comparator changes breaking ordering invariants.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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