nautechsystems/nautilus_trader · error

Market connection pool is closed

Error message

Market connection pool is closed

What it means

PolymarketConnectionPool serializes subscriptions with wire_mutex and tracks a closed flag. subscribe_new_markets_feed first takes the wire lock, then checks `closed`; if the pool has already been shut down it cannot establish the new-markets feed and returns this error instead.

Source

Thrown at crates/adapters/polymarket/src/websocket/pool.rs:278

        *self.inner.out_tx.lock() = Some(out_tx);
        *self.inner.out_rx.lock() = Some(out_rx);

        self.inner.connect_new_shard(true).await?;
        Ok(())
    }

    /// Sends the new-market discovery subscribe on the primary shard.
    ///
    /// # Errors
    ///
    /// Returns an error if no primary shard is available.
    pub async fn subscribe_new_markets_feed(&self) -> anyhow::Result<()> {
        let _wire = self.inner.wire_mutex.lock().await;

        let handle = {
            let state = self.inner.state.lock();
            if self.inner.closed.load(Ordering::Acquire) {
                anyhow::bail!("Market connection pool is closed");
            }
            state
                .shards
                .get(&PRIMARY_SHARD_ID)
                .map(|shard| shard.handle.clone())
        };

        match handle {
            Some(handle) => handle.subscribe_market(vec![]).await,
            None => anyhow::bail!("No primary market shard available for new-market discovery"),
        }
    }

    /// Takes the merged message receiver, leaving `None` in its place.
    #[must_use]
    pub fn take_message_receiver(
        &self,
    ) -> Option<tokio::sync::mpsc::UnboundedReceiver<PolymarketWsMessage>> {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check pool.is_closed() (or equivalent) before subscribing, and recreate the pool with connect() if closed
  2. Fix shutdown ordering so subscribers stop before the pool is disconnected
  3. Recreate or reconnect the pool and retry subscribe_new_markets_feed
  4. Hold a single long-lived pool reference instead of caching disconnected instances

Example fix

// before
pool.subscribe_new_markets_feed().await?;
// after
if pool.is_closed() {
    pool = PolymarketConnectionPool::connect(...).await?;
}
pool.subscribe_new_markets_feed().await?;
Defensive patterns

Strategy: validation

Validate before calling

if pool.is_closed() {
    pool = PolymarketConnectionPool::connect(config).await?;
}
pool.subscribe_new_markets_feed().await?;

Try / catch

match pool.subscribe_new_markets_feed().await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("pool is closed") => {
        let mut pool = pool;
        pool = reconnect_pool().await?;
        pool.subscribe_new_markets_feed().await?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling subscribe_new_markets_feed() on a pool after disconnect()/close() was invoked (or the pool auto-closed), or on a stale pool reference retained past shutdown.

Common situations: Shutdown ordering bugs where a background task subscribes to the new-markets feed after the pool is closed; reconnect logic operating on an old pool instance; keeping the pool alive across a trader stop/start cycle.

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/90e9cb94189adef5. Report an issue: GitHub.