{"record":{"id":"cadb1f1daea285a2","repo":"nautechsystems/nautilus_trader","slug":"dydx-websocket-stream-task-is-already-registered","errorCode":null,"errorMessage":"dYdX WebSocket stream task is already registered","messagePattern":"dYdX WebSocket stream task is already registered","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/dydx/src/execution/mod.rs","lineNumber":308,"sourceCode":"        self.order_contexts.insert(client_id_u32, context);\n    }\n\n    fn get_order_context(&self, client_id_u32: u32) -> Option<OrderContext> {\n        self.order_contexts\n            .get(&client_id_u32)\n            .map(|r| r.value().clone())\n    }\n\n    fn get_chain_id(&self) -> ChainId {\n        self.config.get_chain_id()\n    }\n\n    fn spawn_ws_stream_handler(\n        &self,\n        stream: impl Stream<Item = DydxWsOutputMessage> + Send + 'static,\n    ) -> anyhow::Result<()> {\n        if !self.session_tasks.is_empty() {\n            anyhow::bail!(\"dYdX WebSocket stream task is already registered\");\n        }\n\n        log::debug!(\"Starting execution WebSocket message processing task\");\n\n        let trader_id = self.core.trader_id;\n        let account_id = self.core.account_id;\n        let instrument_cache = self.instrument_cache.clone();\n        let oracle_prices = self.oracle_prices.clone();\n        let encoder = self.encoder.clone();\n        let order_contexts = self.order_contexts.clone();\n        let order_id_map = self.order_id_map.clone();\n        let dispatch_state = self.dispatch_state.clone();\n        let block_time_monitor = self.block_time_monitor.clone();\n        let emitter = self.emitter.clone();\n        let clock = self.clock;\n\n        let future = async move {\n            log::debug!(\"Execution WebSocket message loop started\");","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/dydx/src/execution/mod.rs#L290-L326","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure connect() is called only once per client instance","Fully disconnect/tear down (clearing session_tasks) before reconnecting and spawning a new handler","Create a fresh execution client instance instead of reusing one after shutdown"],"exampleFix":"// before\nclient.connect().await?;\nclient.connect().await?; // second spawn fails\n// after\nclient.connect().await?;\n// on reconnect: disconnect first, then\nclient.connect().await?;","handlingStrategy":"validation","validationCode":"// guard in caller\nif client.is_connected() {\n    return Ok(()); // already connected; do not call connect again\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call connect() once per client instance; track connected state","Always disconnect/teardown fully before reconnecting","On reconnect, create a new client instance rather than reusing a live one"],"tags":["rust","dydx-exchange","websocket","invalid-state","duplicate-task"],"backgroundTag":"invalid-state-transition","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}