nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send UpdateTradeSubs command: {e}

Error message

Failed to send UpdateTradeSubs command: {e}

What it means

This error wraps a failed send of UpdateTradeSubs on the internal mpsc command channel during trades subscription. The command publishes the reference-counted stream uses for the coin so the handler keeps its registry in sync; the send fails only when the handler task's receiver is closed. It means the client's handler loop is no longer running.

Source

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

        let coin = instrument.raw_symbol().inner();

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

        // Update the handler's coin→instrument mapping for this subscription
        cmd_tx
            .send(HandlerCommand::UpdateInstrument(instrument.clone()))
            .map_err(|e| anyhow::anyhow!("Failed to send UpdateInstrument command: {e}"))?;

        // Keep registry mutations and their handler commands ordered across
        // concurrent generic/custom subscriptions for the same coin.
        let _trade_stream_guard = self.trade_stream_lock.lock();
        let registration = self.trade_streams.register(coin, stream_use);
        cmd_tx
            .send(HandlerCommand::UpdateTradeSubs {
                coin,
                uses: registration.uses,
            })
            .map_err(|e| anyhow::anyhow!("Failed to send UpdateTradeSubs command: {e}"))?;

        if registration.subscribe
            && let Err(e) = self.send_subscription(&cmd_tx, SubscriptionRequest::Trades { coin })
        {
            let rollback = self.trade_streams.release(&coin, stream_use);
            let _ = cmd_tx.send(HandlerCommand::UpdateTradeSubs {
                coin,
                uses: rollback.uses,
            });
            return Err(e);
        }
        Ok(())
    }

    /// Subscribe to mark price updates for an instrument.
    pub async fn subscribe_mark_prices(&self, instrument_id: InstrumentId) -> anyhow::Result<()> {
        self.subscribe_asset_context_data(instrument_id, AssetContextDataType::MarkPrice)
            .await

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Keep the handler task alive (spawn it and hold its JoinHandle) for as long as the client is used.
  2. If the handler exited, re-create the client and resubscribe; the channel is unrecoverable.
  3. Serialize disconnect and subscribe operations so shutdown cannot race a subscription attempt.
  4. Enable panic hooks/logging on the handler task to find why the loop ended.

Example fix

// before
if shutdown_started {
    tokio::spawn(client.run()); // spawned during shutdown, immediately cancelled
}
client.subscribe_trades(instrument_id).await?;

// after
if !shutdown_started {
    let handle = tokio::spawn(client.clone().run());
    client.subscribe_trades(instrument_id).await?; // handler still alive
    let _ = handle;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// rust
if !client.is_connected() {
    return Err(anyhow::anyhow!("cannot subscribe trades: handler not running"));
}

Try / catch

// rust
if let Err(e) = client.subscribe_trades(instrument_id).await {
    if e.to_string().contains("UpdateTradeSubs") {
        tracing::warn!("handler channel closed ({e}); reconnecting");
        client = reconnect_client().await?;
        client.subscribe_trades(instrument_id).await?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling subscribe_trades when the handler task exited between client creation and this send — after disconnect(), a handler panic, or dropping the task/receiver.

Common situations: Reusing a client after teardown; handler task aborted by its JoinHandle; process/runtime shutdown racing a subscribe; a panic inside the handler loop earlier in the session.

Related errors


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