{"record":{"id":"4a08808697a028c7","repo":"nautechsystems/nautilus_trader","slug":"polymarket-websocket-handler-did-not-stop-after-ab","errorCode":null,"errorMessage":"Polymarket WebSocket handler did not stop after abort","messagePattern":"Polymarket WebSocket handler did not stop after abort","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/polymarket/src/websocket/client.rs","lineNumber":459,"sourceCode":"        log::debug!(\"Disconnecting Polymarket WebSocket\");\n        self.signal.store(true, Ordering::Relaxed);\n\n        if let Err(e) = self.cmd_tx.read().await.send(HandlerCommand::Disconnect) {\n            log::debug!(\"Failed to send disconnect (handler may already be shut down): {e}\");\n        }\n\n        let task_result = match finish_task(\n            &mut self.task_handle,\n            std::time::Duration::from_secs(2),\n            std::time::Duration::from_secs(2),\n        )\n        .await\n        {\n            None | Some(TaskJoinOutcome::Completed(()) | TaskJoinOutcome::Aborted) => Ok(()),\n            Some(TaskJoinOutcome::Failed(error)) => Err(anyhow::anyhow!(\n                \"Polymarket WebSocket handler failed: {error}\"\n            )),\n            Some(TaskJoinOutcome::Incomplete) => Err(anyhow::anyhow!(\n                \"Polymarket WebSocket handler did not stop after abort\"\n            )),\n        };\n        // Invalidate after the task has stopped so any in-flight auth_tracker.succeed()\n        // calls from the handler cannot race with and survive the invalidation.\n        self.auth_tracker.invalidate();\n\n        if let Some(control) = &self.socket_control {\n            control.deregister();\n        }\n        log::debug!(\"Polymarket WebSocket disconnected\");\n        task_result\n    }\n\n    /// Returns `true` if the WebSocket is actively connected.\n    #[must_use]\n    pub fn is_active(&self) -> bool {\n        ConnectionMode::from_atomic(&self.connection_mode).is_active()","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/polymarket/src/websocket/client.rs#L441-L477","documentation":"disconnect() aborts the feed-handler task and waits up to 2 seconds for it to finish. TaskJoinOutcome::Incomplete means the task neither completed, failed, nor acknowledged the abort within that window — it is stuck (e.g. blocked in a non-cancellation-safe await). The disconnect call reports this as an error because a stale handler task may still hold the WebSocket connection and channel receivers.","triggerScenarios":"Calling disconnect() while the handler task is blocked in an await point that ignores cancellation — e.g. a long blocking read, a lock held across awaits, or a reconnect loop stuck on TCP connect to an unresponsive host without timeouts.","commonSituations":"Network partition causing a connect attempt with no timeout to hang; handler waiting on a mutex starved by another task; very slow/broken gateway that stalls the read loop past the 2s join window.","solutions":["Ensure all connect/read/write operations inside the handler use timeouts (tokio::time::timeout) so abort is honored promptly","Retry disconnect() once — the stuck await may complete and let a second join succeed","Avoid holding locks across .await points in the handler loop","If a stale task remains, drop the whole client so the channel closes and the task unwinds eventually"],"exampleFix":"// before\nclient.disconnect().await?; // may fail: handler stuck >2s after abort\n// after\nif let Err(e) = client.disconnect().await {\n    log::warn!(\"handler did not stop in time, dropping client: {e}\");\n    drop(client); // dropping closes channels and lets the task unwind\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(e) = client.disconnect().await {\n    if e.to_string().contains(\"did not stop after abort\") {\n        tokio::time::sleep(Duration::from_millis(100)).await;\n        client.disconnect().await.ok(); // second join usually succeeds\n    }\n}","preventionTips":["Ensure every await in the handler loop is cancellation-safe and timeout-bounded","Never hold a mutex/lock across .await inside the handler","Add connect/read timeouts so a hung TCP attempt cannot block abort","Drop the whole client if a second disconnect also fails, letting channel closure unwind the task"],"tags":["rust","websocket","task","timeout","polymarket"],"backgroundTag":"task-shutdown-timeout","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"}