nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send SetDepth10Sub command

Error message

Failed to send SetDepth10Sub command: {e}

What it means

This error wraps a failed send of the SetDepth10Sub(subscribed: true) command on the client's internal mpsc command channel. It is thrown right after the UpdateInstrument registration during depth-10 subscription, so the handler task stopped accepting commands between the two sends. It indicates the WebSocket handler receiver is closed.

Solutions

  1. Verify the handler task is alive (JoinHandle not finished) before subscribing; respawn/reconnect if it exited.
  2. Avoid calling subscribe concurrently with disconnect/shutdown; sequence them or guard with application-level state.
  3. Re-create the client if the handler loop exited; the channel cannot be revived.
  4. Check handler-task logs/panic hooks for the root cause of the loop exiting.

Example fix

// before
client.disconnect().await?;
client.subscribe_book_depth10(instrument_id).await?; // channel already closed

// after
if !client.is_connected() {
    client.connect().await?;
}
client.subscribe_book_depth10(instrument_id).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// rust
if !client.is_connected() {
    return Err(anyhow::anyhow!("client not connected before depth10 subscribe"));
}

Try / catch

// rust
if let Err(e) = client.subscribe_book_depth10(instrument_id).await {
    if e.to_string().contains("SetDepth10Sub") {
        tracing::warn!("handler channel closed ({e}); re-establishing client");
        client = reconnect_client().await?;
        client.subscribe_book_depth10(instrument_id).await?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling subscribe_book_depth10 when the handler task has already terminated — after disconnect(), after the handler task panicked or its JoinHandle/receiver was dropped, or on a client whose run loop exited between the preceding UpdateInstrument send and this one.

Common situations: Subscribe racing with shutdown/disconnect; handler task crashed earlier (check its logs); client used after being moved into a scope that ended; runtime shutdown during teardown.

Related errors


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

Appendix: source

Thrown at crates/adapters/hyperliquid/src/websocket/client.rs:1405

        mantissa: Option<u32>,
    ) -> anyhow::Result<()> {
        let instrument = self
            .get_instrument(&instrument_id)
            .ok_or_else(|| InstrumentLookupError::not_found(instrument_id))?;
        let coin = instrument.raw_symbol().inner();

        let cmd_tx = self.cmd_tx.read().await;

        cmd_tx
            .send(HandlerCommand::UpdateInstrument(instrument.clone()))
            .map_err(|e| anyhow::anyhow!("Failed to send UpdateInstrument command: {e}"))?;

        cmd_tx
            .send(HandlerCommand::SetDepth10Sub {
                coin,
                subscribed: true,
            })
            .map_err(|e| anyhow::anyhow!("Failed to send SetDepth10Sub command: {e}"))?;

        self.send_book_stream_subscribe(&cmd_tx, coin, BookStreamUse::Depth10, n_sig_figs, mantissa)
    }

    /// Unsubscribe from order book depth-10 snapshots.
    ///
    /// Clears the depth10 emission flag and tears down the underlying
    /// `l2Book` stream unless active deltas subscribers still need it.
    pub async fn unsubscribe_book_depth10(
        &self,
        instrument_id: InstrumentId,
    ) -> anyhow::Result<()> {
        let instrument = self
            .get_instrument(&instrument_id)
            .ok_or_else(|| InstrumentLookupError::not_found(instrument_id))?;
        let coin = instrument.raw_symbol().inner();

        let cmd_tx = self.cmd_tx.read().await;

View on GitHub (pinned to 18893faf8b)