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

  1. Create a new DatabentoLive client instance instead of reusing a closed one.
  2. Track client lifecycle in your orchestration code and only call start on fresh clients.
  3. If reconnection is needed, reconstruct the client with the same credentials and re-subscribe.
  4. 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

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


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