nautechsystems/nautilus_trader · error

Client already running

Error message

Client already running

What it means

`start` checks `is_running` before spinning up the feed handler and message channel. Calling start on an already-running client is refused with this error so the client never runs two feed handlers or leaks duplicate channels.

Source

Thrown at crates/adapters/databento/src/live.rs:260

    }

    /// 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,
            msg_tx,
            self.publisher_venue_map.clone(),
            self.symbol_venue_map.clone(),
            self.use_exchange_as_venue,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Call `start` only once per client lifecycle; check an `is_running` flag or state getter before calling.
  2. Synchronize startup behind a lock/async gate so only one caller starts the client.
  3. If a fresh start is truly needed, call `close()` first, then construct a new client.
  4. Wrap start in idempotent helper that returns the existing channel if already running.

Example fix

// before
client.start().await?;
client.start().await?; // error: already running
// after
if !client.is_running() {
    client.start().await?;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Idempotent start gate
let started = AtomicBool::new(false);
if !started.swap(true, Ordering::SeqCst) {
    client.start().await?;
}

Try / catch

// Treat 'already running' as success
match client.start().await {
    Ok(pair) => pair,
    Err(e) if e.to_string().contains("already running") => {
        // obtain existing rx via a getter instead
        unreachable_or_fetch_existing()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `start` (or `py_start`) twice on the same client without an intervening `close()`.

Common situations: Concurrent tasks racing to start the same client; retry logic re-invoking start after a timeout; startup code accidentally duplicated in strategy and kernel layers.

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/67657b3f57c74bf2. Report an issue: GitHub.