nautechsystems/nautilus_trader · error · anyhow::Error

Command receiver already taken

Error message

Command receiver already taken

What it means

`DatabentoLiveClient::start` takes the command receiver (`cmd_rx`) out of an `Option` that is consumed on first start; calling `start` a second time finds `None` and raises this error. It enforces the one-shot lifecycle of the client's internal command channel — a client instance cannot be started twice.

Source

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

    ) -> 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,
            self.bars_timestamp_on_close,
            self.reconnect_timeout_mins,
        );

        self.send_command(HandlerCommand::Start)?;
        self.is_running = true;

        Ok((feed_handler, msg_rx))
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Create a new `DatabentoLiveClient` instance instead of restarting the consumed one
  2. Track client state and only call `start` once per instance
  3. On failure/reconnect, replace the client (rebuild via constructor) rather than re-calling start
  4. Check whether a previous `start`/`stop` already consumed the receiver before calling start

Example fix

// before
if client_failed {
    client.start().await?; // panics with "Command receiver already taken"
}

// after
if client_failed {
    client = DatabentoLiveClient::new(/* same config */)?;
    client.start().await?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before start
if client.is_running() {
    anyhow::bail!("client already started");
}

Type guard

fn can_start(client: &DatabentoLiveClient) -> bool {
    !client.is_started() // track a started flag in your wrapper
}

Try / catch

match client.start().await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Command receiver already taken") => {
        log::warn!("client already started; rebuilding instance");
        client = DatabentoLiveClient::new(/* config */)?;
        client.start().await?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `start` (or `py_start`) twice on the same live client instance; restarting a client after a session ended without constructing a fresh client; calling start after `stop` or after a previous run consumed the receiver.

Common situations: Restart logic in a long-running process reusing the old client object; exception handlers that retry by calling `start` again instead of rebuilding the client; Python wrappers inadvertently invoking start twice (e.g. both framework and user code).

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/65e5ece1e6a234ba. Report an issue: GitHub.