nautechsystems/nautilus_trader · error

MBO delta tail must be queued

Error message

MBO delta tail must be queued

What it means

`release` finalizes an incomplete MBO delta sequence by marking its tail element ready. It computes the tail's offset in the internal queue as `tail - head` and panics if the tail index is not representable (negative or overflowing usize), meaning the recorded tail is not actually in the current queue window.

Source

Thrown at crates/adapters/databento/src/decode/market_data.rs:298

    }

    fn release(&mut self, instrument_id: InstrumentId, flags: u8) {
        let tail = match self.tail {
            Some((tail_instrument_id, tail)) if tail_instrument_id == instrument_id => {
                self.tail = None;
                tail
            }
            _ => {
                let Some(tail) = self.tails.remove(&instrument_id) else {
                    return;
                };
                tail
            }
        };
        let offset = tail
            .checked_sub(self.head)
            .and_then(|offset| usize::try_from(offset).ok())
            .expect("MBO delta tail must be queued");
        let queued = self
            .queue
            .get_mut(offset)
            .expect("MBO delta tail must be queued");
        queued.delta.flags |= flags;
        queued.ready = true;
    }
}

/// Decodes a Databento Trade message into a `TradeTick`.
///
/// # Errors
///
/// Returns an error if decoding the Trade message fails.
pub fn decode_trade_msg(
    msg: &dbn::TradeMsg,
    instrument_id: InstrumentId,
    price_precision: u8,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check that `finish()`/`pop_ready()` clears `self.tail` when the queue drains, so stale tails never survive past their elements.
  2. Recreate the decoder/buffer for the affected instrument to clear stale state.
  3. Report upstream with the instrument_id and message sequence if reproducible; it indicates a bookkeeping bug in the buffer.

Example fix

// before
self.release(instrument_id, 0); // stale tail from earlier pops survives
// after (in pop_ready/drain path)
if self.queue.is_empty() {
    self.tail = None;
    self.head = self.next;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before streaming, ensure a fresh buffer per instrument
let buffer = MboDeltaBuffer::new();

Try / catch

// panics are not catchable in Rust safely; wrap decode in a task and treat panic as feed failure
let result = std::panic::catch_unwind(|| decoder.handle(msg));
if result.is_err() { reconnect_feed(); }

Prevention

When it happens

Trigger: Calling `release` with a stored tail index that predates already-popped entries (tail < head), or an integer overflow in `tail - head` — i.e. the buffer's tail bookkeeping is out of sync with head.

Common situations: A decoder bug that pops deltas without clearing `self.tail`, or mixing buffer instances / reusing a buffer across messages where a stale tail from a previous session lingers. Also triggered by usize overflow on 32-bit platforms with huge queues.

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/2c934f93ea2f41da. Report an issue: GitHub.