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

  1. Ensure close_connections() is called exactly once from a single owner task; guard with an AtomicBool/OnceCell.
  2. Do not call shutdown from inside a connection handler task that the shutdown itself would stop.
  3. Inspect the embedded {e}: SendError means the handler task already exited; join handler tasks before closing.
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/499cd18adecf0012. Report an issue: GitHub.