{"record":{"id":"57a881746369ce84","repo":"nautechsystems/nautilus_trader","slug":"e-subscription-rollback-failed-close-error","errorCode":null,"errorMessage":"{e}; subscription rollback failed: {close_error}","messagePattern":"(.+?); subscription rollback failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/polymarket/src/websocket/pool.rs","lineNumber":529,"sourceCode":"            out_rx: Mutex::new(None),\n            socket_factory: Mutex::new(None),\n            closed: AtomicBool::new(false),\n        }\n    }\n\n    // Callers hold `wire_mutex`.\n    async fn subscribe_one(&self, asset_id: String) -> anyhow::Result<()> {\n        let token = Ustr::from(asset_id.as_str());\n\n        let Some(handle) = self.assign(token).await? else {\n            return Ok(());\n        };\n\n        if let Err(e) = self.ensure_open() {\n            if let ReleaseOutcome::CloseShard(id, shard) = self.release(token)\n                && let Err(close_error) = self.close_shard(id, shard).await\n            {\n                anyhow::bail!(\"{e}; subscription rollback failed: {close_error}\");\n            }\n            return Err(e);\n        }\n\n        if let Err(e) = handle.subscribe_market(vec![asset_id]).await {\n            // Roll back so a failed send leaves no stale assignment or empty shard.\n            if let ReleaseOutcome::CloseShard(id, shard) = self.release(token)\n                && let Err(close_error) = self.close_shard(id, shard).await\n            {\n                anyhow::bail!(\"{e}; subscription rollback failed: {close_error}\");\n            }\n            return Err(e);\n        }\n        Ok(())\n    }\n\n    // Callers hold `wire_mutex`.\n    async fn unsubscribe_one(&self, asset_id: String) -> anyhow::Result<()> {","sourceCodeStart":511,"sourceCodeEnd":547,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/polymarket/src/websocket/pool.rs#L511-L547","documentation":"When subscribe_one re-checks pool openness after a shard was assigned, finding the pool closed triggers a rollback: the token assignment is released and, if the shard became empty, it is closed. If that rollback close itself fails, the original 'pool is closed' error and the rollback close error are chained into this combined message; otherwise the original error is returned alone.","triggerScenarios":"A disconnect/close races with subscribe_one: the pool closes between the initial ensure_open and the post-assignment ensure_open, AND close_shard fails during rollback (e.g., the shard's connection is already dead).","commonSituations":"Adapter shutdown while subscription requests are in flight; simultaneous disconnect and subscribe from different tasks.","solutions":["Treat both errors as terminal: the pool is closed — reconnect with a new pool instead of retrying","Avoid racing subscribe against disconnect; cancel subscriptions before initiating shutdown","Log both the original and rollback errors from the combined message for diagnosis","If the rollback failure indicates an already-dead connection, cleanup should tolerate it — retry disconnect/drop the pool"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if pool.is_closed() { pool = reconnect_pool()?; }","typeGuard":null,"tryCatchPattern":"match pool.subscribe_one(asset_id).await {\n    Err(e) if e.to_string().contains(\"pool is closed\") => {\n        let pool = reconnect_pool()?;\n        pool.subscribe_one(asset_id).await?;\n    }\n    other => other?,\n}","preventionTips":["Cancel in-flight subscriptions before shutdown","Use a lifecycle owner to order connect/subscribe/disconnect","Treat chained rollback errors as terminal closed-pool conditions","Avoid sharing pool handles across tasks that also disconnect"],"tags":["websocket","polymarket","race-condition","rollback"],"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-14T00:17:10.932Z"}