{"record":{"id":"499cd18adecf0012","repo":"nautechsystems/nautilus_trader","slug":"binance-spot-public-json-stream-pool-shutdown-bega-499cd1","errorCode":null,"errorMessage":"Binance Spot public JSON stream pool shutdown began during subscribe; rollback failed: {e}","messagePattern":"Binance Spot public JSON stream pool shutdown began during subscribe; rollback failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/binance/src/spot/websocket/public_json/client.rs","lineNumber":352,"sourceCode":"                break;\n            }\n\n            let new_slot = self.create_connection(slot_count).await?;\n            let (slot_count, shutdown) = {\n                let mut slots = self.slots.lock();\n                let shutdown = self.signal.load(Ordering::Acquire);\n                slots.push(new_slot);\n                (slots.len(), shutdown)\n            };\n\n            if shutdown {\n                let client = self.clone();\n                let rollback = client.close_connections().await;\n                return Err(match rollback {\n                    Ok(()) => anyhow::anyhow!(\n                        \"Binance Spot public JSON stream pool shutdown began during subscribe\"\n                    ),\n                    Err(e) => anyhow::anyhow!(\n                        \"Binance Spot public JSON stream pool shutdown began during subscribe; rollback failed: {e}\"\n                    ),\n                });\n            }\n            log::debug!(\n                \"Spot JSON pool slot {} connected: url={}\",\n                slot_count - 1,\n                self.url\n            );\n        }\n\n        // Phase 3: stage assignments, send commands, then commit slot state.\n        let mut slots = self.slots.lock();\n\n        if self.signal.load(Ordering::Acquire) {\n            anyhow::bail!(\"Binance Spot public JSON stream pool is shutting down\");\n        }\n        let mut slot_batches: Vec<(usize, Vec<String>)> = Vec::new();","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/binance/src/spot/websocket/public_json/client.rs#L334-L370","documentation":"Same shutdown-during-subscribe guard as the plain variant, but here the rollback via close_connections() itself returned an error, which is interpolated into the message. This indicates both a shutdown race and a failure to cleanly close the pool's connections/handlers.","triggerScenarios":"subscribe() overlaps shutdown AND close_connections() fails — typically because handler tasks/cmd channels were already dropped or joined, so sending close commands fails.","commonSituations":"Double shutdown calls, or shutdown invoked from within a handler task that owns the pool, causing close commands to hit dead channels during teardown.","solutions":["Ensure close_connections() is called exactly once from a single owner task; guard with an AtomicBool/OnceCell.","Do not call shutdown from inside a connection handler task that the shutdown itself would stop.","Inspect the embedded {e}: SendError means the handler task already exited; join handler tasks before closing.","Suppress this error during intentional shutdown after logging, since the pool is going away anyway."],"exampleFix":"// before\n// called from multiple places\npool.close_connections().await?;\n\n// after\nif pool.shutdown_flag.swap(true, Ordering::SeqCst) {\n    return Ok(()); // already shut down\n}\npool.close_connections().await?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.subscribe(streams).await {\n    Err(e) if e.to_string().contains(\"rollback failed\") => {\n        log::error!(\"shutdown rollback failed during subscribe: {e}\"); // inspect embedded cause\n    }\n    Err(e) => return Err(e),\n    Ok(v) => Ok(v),\n}","preventionTips":["Call close_connections() exactly once via a OnceCell/AtomicBool guard.","Never invoke shutdown from inside a handler task owned by the pool.","Join handler tasks before closing channels so rollback sends succeed.","Log the embedded {e} to find which handler/channel was already dead."],"tags":["race-condition","shutdown","websocket","double-close"],"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"}