nautechsystems/nautilus_trader · warning

Binance Spot public JSON stream pool is shutting down

Error message

Binance Spot public JSON stream pool is shutting down

What it means

subscribe checks the pool's shutdown signal (atomic flag) while filtering already-subscribed streams under the slots lock; if the pool is shutting down it refuses new subscriptions. This is phase 1 of a three-phase subscribe protocol, guarding against subscribing to a closing pool.

Source

Thrown at crates/adapters/binance/src/spot/websocket/public_json/client.rs:310

        }
        log::debug!("Disconnected from Binance Spot public JSON stream pool");
        Ok(())
    }

    /// Subscribes to stream names.
    ///
    /// # Errors
    ///
    /// Returns an error if command delivery fails or if the connection pool is exhausted.
    pub async fn subscribe(&self, streams: Vec<String>) -> anyhow::Result<()> {
        let _connect_guard = self.connect_lock.lock().await;

        // Phase 1: filter already-subscribed streams (brief lock)
        let new_streams: Vec<String> = {
            let slots = self.slots.lock();

            if self.signal.load(Ordering::Acquire) {
                anyhow::bail!("Binance Spot public JSON stream pool is shutting down");
            }
            streams
                .into_iter()
                .filter(|s| !slots.iter().any(|slot| slot.streams.contains(s)))
                .collect()
        };

        if new_streams.is_empty() {
            return Ok(());
        }

        // Phase 2: create connections if needed (no lock held during async connect)
        loop {
            let (remaining_capacity, slot_count) = {
                let slots = self.slots.lock();
                let cap: usize = slots
                    .iter()
                    .map(|s| MAX_STREAMS_PER_CONNECTION.saturating_sub(s.streams.len()))

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Stop issuing subscribe calls before closing the pool; cancel producer tasks that call subscribe during shutdown.
  2. Treat the error as benign during application shutdown and swallow it where appropriate.
  3. Sequence shutdown: unsubscribe/close all consumers first, then call close.
  4. If this occurs unexpectedly, check for leaked background tasks still calling subscribe after close was initiated.

Example fix

// before
handle.subscribe(streams).await?;
// after
if app_is_shutting_down() {
    log::debug!("skip subscribe, pool shutting down");
} else {
    handle.subscribe(streams).await?;
}
Defensive patterns

Strategy: try-catch

Try / catch

match handle.subscribe(streams).await {
    Err(e) if e.to_string().contains("shutting down") => log::debug!("pool closed, skipping subscribe"),
    other => other?,
}

Prevention

When it happens

Trigger: Calling subscribe (public) concurrently with close_connections/close: the shutdown signal was set (Ordering::Acquire observed true) before or during the phase-1 filtering, so any new stream subscription is rejected.

Common situations: A racing task subscribes while the user closes the client; tests intentionally exercising shutdown races; application shutdown ordering where a data consumer keeps subscribing after stream teardown begins.

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/5e4d085f87ce6b3d. Report an issue: GitHub.