nautechsystems/nautilus_trader · error · anyhow::Error

dYdX WebSocket stream task is already registered

Error message

dYdX WebSocket stream task is already registered

What it means

The dYdX execution client expects exactly one WebSocket stream processing task for its lifetime. spawn_ws_stream_handler checks session_tasks before spawning and bails if a stream handler task already exists, preventing duplicate message processing and double-handling of account/order events.

Source

Thrown at crates/adapters/dydx/src/execution/mod.rs:308

        self.order_contexts.insert(client_id_u32, context);
    }

    fn get_order_context(&self, client_id_u32: u32) -> Option<OrderContext> {
        self.order_contexts
            .get(&client_id_u32)
            .map(|r| r.value().clone())
    }

    fn get_chain_id(&self) -> ChainId {
        self.config.get_chain_id()
    }

    fn spawn_ws_stream_handler(
        &self,
        stream: impl Stream<Item = DydxWsOutputMessage> + Send + 'static,
    ) -> anyhow::Result<()> {
        if !self.session_tasks.is_empty() {
            anyhow::bail!("dYdX WebSocket stream task is already registered");
        }

        log::debug!("Starting execution WebSocket message processing task");

        let trader_id = self.core.trader_id;
        let account_id = self.core.account_id;
        let instrument_cache = self.instrument_cache.clone();
        let oracle_prices = self.oracle_prices.clone();
        let encoder = self.encoder.clone();
        let order_contexts = self.order_contexts.clone();
        let order_id_map = self.order_id_map.clone();
        let dispatch_state = self.dispatch_state.clone();
        let block_time_monitor = self.block_time_monitor.clone();
        let emitter = self.emitter.clone();
        let clock = self.clock;

        let future = async move {
            log::debug!("Execution WebSocket message loop started");

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure connect() is called only once per client instance
  2. Fully disconnect/tear down (clearing session_tasks) before reconnecting and spawning a new handler
  3. Create a fresh execution client instance instead of reusing one after shutdown

Example fix

// before
client.connect().await?;
client.connect().await?; // second spawn fails
// after
client.connect().await?;
// on reconnect: disconnect first, then
client.connect().await?;
Defensive patterns

Strategy: validation

Validate before calling

// guard in caller
if client.is_connected() {
    return Ok(()); // already connected; do not call connect again
}

Prevention

When it happens

Trigger: Calling spawn_ws_stream_handler (typically from connect) when session_tasks is non-empty — i.e. connect() invoked twice on the same client, or reconnect logic spawning a second handler without shutting down the first.

Common situations: Re-connecting after a network drop without tearing down the previous session; a framework lifecycle bug that calls connect more than once on the same client instance.

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