{"record":{"id":"06bba1e5b21bff66","repo":"nautechsystems/nautilus_trader","slug":"errors-join-aggregated-shutdown-errors","errorCode":null,"errorMessage":"errors.join(\"; \") (aggregated shutdown errors)","messagePattern":"errors\\.join\\(\"; \"\\) \\(aggregated shutdown errors\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/adapters/coinbase/src/websocket/client.rs","lineNumber":538,"sourceCode":"\n            if tokio::time::Instant::now() >= deadline {\n                self.shutdown_errors\n                    .push(\"Timed out waiting for WebSocket to reach Closed state\".to_string());\n                break;\n            }\n\n            tokio::time::sleep(Duration::from_millis(20)).await;\n        }\n\n        if let Some(control) = &self.socket_control {\n            control.deregister();\n        }\n\n        if self.shutdown_errors.is_empty() {\n            Ok(())\n        } else {\n            let errors = std::mem::take(&mut self.shutdown_errors);\n            anyhow::bail!(errors.join(\"; \"))\n        }\n    }\n\n    /// Returns true if the WebSocket connection is active.\n    #[must_use]\n    pub fn is_active(&self) -> bool {\n        let mode_ptr = self.connection_mode.load();\n        let mode_val = mode_ptr.load(Ordering::Relaxed);\n        ConnectionMode::from_u8(mode_val).is_active()\n    }\n\n    /// Returns true if the WebSocket is reconnecting after a transport drop.\n    #[must_use]\n    pub fn is_reconnecting(&self) -> bool {\n        let mode_ptr = self.connection_mode.load();\n        let mode_val = mode_ptr.load(Ordering::Relaxed);\n        ConnectionMode::from_u8(mode_val).is_reconnect()\n    }","sourceCodeStart":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/coinbase/src/websocket/client.rs#L520-L556","documentation":"disconnect() collects any errors encountered while shutting down the WebSocket (handler join failures, command send failures) into shutdown_errors and, if non-empty, fails with all of them joined by \"; \". It signals the shutdown path partially failed even though the disconnect was requested.","triggerScenarios":"Calling disconnect() after the handler already errored/died, or when aborting the task or draining the output channel fails; the accumulated errors are reported together.","commonSituations":"App shutdown racing an already-broken WebSocket; repeated disconnect() calls; underlying network failure during the final unsubscribe/teardown.","solutions":["Read the joined messages to identify which teardown step(s) failed","Ignore or log the error if the goal was merely to stop consuming (connection is gone anyway)","Ensure connect() succeeded and is_active() before disconnecting to avoid teardown on a dead connection"],"exampleFix":"// before\nws.disconnect().await?;\n// after\nif ws.is_active() {\n    if let Err(e) = ws.disconnect().await { log::warn!(\"shutdown errors: {e}\"); }\n}","handlingStrategy":"try-catch","validationCode":"if !ws.is_active() { return Ok(()); } // nothing to disconnect","typeGuard":null,"tryCatchPattern":"if let Err(e) = ws.disconnect().await {\n    // best-effort shutdown: log joined errors instead of failing the app\n    log::warn!(\"Coinbase ws shutdown issues: {e}\");\n}","preventionTips":["Treat shutdown errors as best-effort warnings in application teardown paths","Check is_active() before disconnecting","Avoid disconnecting twice; track connection state"],"tags":["rust","coinbase","websocket","shutdown"],"backgroundTag":"websocket-connection-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}