nautechsystems/nautilus_trader · error
Close command should not be drained
Error message
Close command should not be drained
What it means
In the Redis cache's buffer-draining loop (crates/infrastructure/src/redis/cache.rs), `drain_buffer` matches on `DatabaseOperation` variants and panics if it encounters `DatabaseOperation::Close`. Control commands like Close are lifecycle signals that must be handled by the command-processing logic, not persisted/drained as data operations; reaching this branch means an internal invariant was violated — a Close op leaked into the operation buffer.
Source
Thrown at crates/infrastructure/src/redis/cache.rs:869
} else {
log::error!("Null `payload` for `replace_list`");
}
}
DatabaseOperation::Delete => {
log::debug!(
"Processing DELETE for collection: {}, key: {}, payload: {:?}",
collection,
key,
msg.payload.as_ref().map(std::vec::Vec::len)
);
// `payload` can be `None` for a delete operation
if let Err(e) = delete(&mut pipe, collection, &key, msg.payload) {
log::error!("{e}");
} else {
has_pending_ops = true;
}
}
DatabaseOperation::Close => panic!("Close command should not be drained"),
DatabaseOperation::Flush(_) => panic!("Flush command should not be drained"),
}
}
flush_pending_pipeline(conn, &mut pipe, &mut has_pending_ops).await;
}
async fn flush_pending_pipeline(
conn: &mut ConnectionManager,
pipe: &mut Pipeline,
has_pending_ops: &mut bool,
) {
if !*has_pending_ops {
return;
}
if let Err(e) = pipe.query_async::<()>(conn).await {
log::error!("{e}");View on GitHub (pinned to 18893faf8b)
Solutions
- Audit how DatabaseOperation::Close enters the buffer; handle it in the control path (e.g. match it in handle_command/process_commands and stop the worker) rather than enqueueing it.
- Filter out Close/Flush variants before pushing operations into the buffer.
- Reproduce with the exact shutdown sequence, capture a backtrace (RUST_BACKTRACE=1), and file/check for an upstream fix.
Example fix
// before
if let Err(e) = cmd_tx.send(DatabaseOperation::Close).await { /* ... */ } // Close goes into data buffer
// after
// signal shutdown through the dedicated control channel / handle_command match arm,
// not by enqueueing DatabaseOperation::Close into the drained buffer Defensive patterns
Strategy: validation
Validate before calling
// never enqueue control ops into the data buffer
match op {
DatabaseOperation::Close | DatabaseOperation::Flush(_) => handle_control(op),
_ => buffer_tx.send(op).await?,
} Try / catch
// panics are unrecoverable here; serialize shutdown so Close is handled before flush_buffer runs shutdown_token.cancel().await; worker.join().await;
Prevention
- Use separate channels for control commands (Close/Flush) and data operations.
- Coordinate shutdown ordering: drain buffers before issuing Close.
- Fuzz/soak-test concurrent write + shutdown paths.
When it happens
Trigger: A `DatabaseOperation::Close` being enqueued into the database command buffer and then processed by `drain_buffer` (called from `process_commands`, `handle_command`, or `flush_buffer`) instead of being consumed as a shutdown/control signal.
Common situations: Racing a cache shutdown/flush with in-flight writes so a Close command lands in the op queue; custom code or an older version constructing/enqueueing Close into the command channel; bugs in internal command routing during teardown.
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
- Flush command should not be drained
- Redis config error: username supplied without password. Eith
- Order invariant violated: first event must be OrderInitializ
- Failed to send delete order command: {e}
- Empty `payload` for `delete` '{key}'
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/56b953efca8f2c99.
Report an issue: GitHub.