nautechsystems/nautilus_trader · error · anyhow::Error
Binance Spot public JSON stream pool shutdown began during s
Error message
Binance Spot public JSON stream pool shutdown began during subscribe; rollback failed: {e} What it means
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.
Source
Thrown at crates/adapters/binance/src/spot/websocket/public_json/client.rs:352
break;
}
let new_slot = self.create_connection(slot_count).await?;
let (slot_count, shutdown) = {
let mut slots = self.slots.lock();
let shutdown = self.signal.load(Ordering::Acquire);
slots.push(new_slot);
(slots.len(), shutdown)
};
if shutdown {
let client = self.clone();
let rollback = client.close_connections().await;
return Err(match rollback {
Ok(()) => anyhow::anyhow!(
"Binance Spot public JSON stream pool shutdown began during subscribe"
),
Err(e) => anyhow::anyhow!(
"Binance Spot public JSON stream pool shutdown began during subscribe; rollback failed: {e}"
),
});
}
log::debug!(
"Spot JSON pool slot {} connected: url={}",
slot_count - 1,
self.url
);
}
// Phase 3: stage assignments, send commands, then commit slot state.
let mut slots = self.slots.lock();
if self.signal.load(Ordering::Acquire) {
anyhow::bail!("Binance Spot public JSON stream pool is shutting down");
}
let mut slot_batches: Vec<(usize, Vec<String>)> = Vec::new();View on GitHub (pinned to 18893faf8b)
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.
Example fix
// before
// called from multiple places
pool.close_connections().await?;
// after
if pool.shutdown_flag.swap(true, Ordering::SeqCst) {
return Ok(()); // already shut down
}
pool.close_connections().await?; Defensive patterns
Strategy: try-catch
Try / catch
match client.subscribe(streams).await {
Err(e) if e.to_string().contains("rollback failed") => {
log::error!("shutdown rollback failed during subscribe: {e}"); // inspect embedded cause
}
Err(e) => return Err(e),
Ok(v) => Ok(v),
} Prevention
- 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.
When it happens
Trigger: subscribe() overlaps shutdown AND close_connections() fails — typically because handler tasks/cmd channels were already dropped or joined, so sending close commands fails.
Common situations: Double shutdown calls, or shutdown invoked from within a handler task that owns the pool, causing close commands to hit dead channels during teardown.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Polymarket RTDS task owner was dropped
- RTDS connection was canceled by shutdown
- Binance Spot public JSON stream pool shutdown began during c
- Binance Spot public JSON stream pool shutdown began during c
- Binance Spot public JSON stream pool shutdown began during s
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/499cd18adecf0012.
Report an issue: GitHub.