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
- Check that `finish()`/`pop_ready()` clears `self.tail` when the queue drains, so stale tails never survive past their elements.
- Recreate the decoder/buffer for the affected instrument to clear stale state.
- 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
- Always call finish() when a subscription ends so stale tails are cleared.
- Never share a buffer across instruments or sessions.
- Ensure the is_last delta of every sequence is pushed before release.
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
- `handle_book_mbo` {IMPL_ERR} `{}`
- Close command should not be drained
- Flush command should not be drained
- NoOrderSide
- Order invariant violated: first event must be OrderInitializ
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/2c934f93ea2f41da.
Report an issue: GitHub.