nautechsystems/nautilus_trader · error
Client already closed
Error message
Client already closed
What it means
The live client's `start` method checks `is_closed` before initializing the feed handler. If the client has already been closed (its `close` was called or close completed), starting is an invalid state transition and the client bails instead of resurrecting a dead client.
Source
Thrown at crates/adapters/databento/src/live.rs:256
}
}
self.send_command(HandlerCommand::Subscribe(sub))
}
/// Starts the live feed handler and returns its message receiver.
///
/// # Errors
///
/// Returns an error if the client is already closed, already running, or cannot start.
pub fn start(
&mut self,
) -> anyhow::Result<(
DatabentoFeedHandler,
tokio::sync::mpsc::UnboundedReceiver<DatabentoMessage>,
)> {
if self.is_closed {
anyhow::bail!("Client already closed");
}
if self.is_running {
anyhow::bail!("Client already running");
}
log::debug!("Starting client");
let (msg_tx, msg_rx) = tokio::sync::mpsc::unbounded_channel::<DatabentoMessage>();
let cmd_rx = self
.cmd_rx
.take()
.ok_or_else(|| anyhow::anyhow!("Command receiver already taken"))?;
let feed_handler = DatabentoFeedHandler::new(
self.credential.clone(),
self.dataset.clone(),
cmd_rx,View on GitHub (pinned to 18893faf8b)
Solutions
- Create a new DatabentoLive client instance instead of reusing a closed one.
- Track client lifecycle in your orchestration code and only call start on fresh clients.
- If reconnection is needed, reconstruct the client with the same credentials and re-subscribe.
- Guard the call: skip start if the client reports closed.
Example fix
// before client.close(); client.start().await?; // panics/bails: already closed // after client.close(); let client = DatabentoLive::new(key, dataset, ...)?; client.start().await?;
Defensive patterns
Strategy: try-catch
Validate before calling
// Skip start if client already terminated
if client.is_closed() {
client = DatabentoLive::new(key, dataset, ...)?;
}
client.start().await?; Try / catch
// Rust
match client.start().await {
Ok((handler, rx)) => { /* use handler */ }
Err(e) if e.to_string().contains("already closed") => {
client = DatabentoLive::new(key, dataset, ...)?;
client.start().await?;
}
Err(e) => return Err(e),
} Prevention
- Treat clients as single-use: one start, one close
- Rebuild client objects on reconnect instead of restarting
- Centralize client lifecycle in a session manager
When it happens
Trigger: Calling `start` (or `py_start`) on a DatabentoLive client after a previous `close()` completed.
Common situations: Restarting a strategy/session on the same client object after teardown; reusing a cached client instance across backtests or reconnect logic; calling start twice where the first lifecycle ended in close.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Client never started
- Client already running
- No feed handler found for dataset: {dataset}
- Command receiver already taken
- Active execution intent {intent_id} was not found
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/5ef954b767de23a1.
Report an issue: GitHub.