nautechsystems/nautilus_trader · error

Failed to start Polymarket WebSocket handler task: {e}

Error message

Failed to start Polymarket WebSocket handler task: {e}

What it means

connect() spawns a tokio task that drives the Polymarket WebSocket read loop and forwards messages through out_rx. If spawning fails (e.g. the runtime is shutting down or no runtime is active), the method restores out_rx to None and aborts the connection attempt with this error.

Source

Thrown at crates/adapters/polymarket/src/websocket/client.rs:386

                                log::error!("Output channel closed, stopping handler");
                            }
                            break;
                        }
                    }
                    None => {
                        if handler.is_stopped() {
                            log::debug!("Stop signal received, ending handler task");
                        } else {
                            log::warn!("Polymarket WebSocket stream ended unexpectedly");
                        }
                        break;
                    }
                }
            }
            log::debug!("Polymarket WebSocket handler task completed");
        }) {
            self.out_rx = None;
            anyhow::bail!("Failed to start Polymarket WebSocket handler task: {e}");
        }
        Ok(())
    }

    fn websocket_config(&self) -> WebSocketConfig {
        // The market endpoint rejects text PING before its initial subscription. Protocol pings
        // keep an idle socket alive until FeedHandler starts the required text heartbeat.
        let heartbeat_payload = match self.channel {
            WsChannel::Market => None,
            WsChannel::User => Some(POLYMARKET_HEARTBEAT_PAYLOAD.to_string()),
        };

        WebSocketConfig {
            url: self.url.clone(),
            headers: vec![],
            heartbeat_interval_secs: Some(POLYMARKET_HEARTBEAT_SECS),
            heartbeat_payload,
            connect_timeout_ms: Some(15_000),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure connect() (and all client async calls) run inside an active, live tokio runtime
  2. Guard reconnection logic so it is cancelled during shutdown (select! on a shutdown signal before spawning)
  3. Check the wrapped error `e` — spawn failures usually mean JoinHandle/panics in the task body; fix the panic
  4. Retry the connect after runtime is confirmed healthy; verify out_rx was reset to None so state stays consistent

Example fix

// before
let handle = tokio::spawn(async { /* read loop */ });
// after
match tokio::runtime::Handle::try_current() {
    Ok(h) => { let handle = h.spawn(async { /* read loop */ }); ... }
    Err(e) => anyhow::bail!("no active tokio runtime: {e}"),
}
Defensive patterns

Strategy: retry

Validate before calling

// ensure an active runtime before connect
if tokio::runtime::Handle::try_current().is_err() {
    anyhow::bail!("PolymarketWebSocketClient::connect requires a live tokio runtime");
}

Try / catch

match client.connect().await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Failed to start Polymarket WebSocket handler task") => {
        tokio::time::sleep(Duration::from_millis(200)).await;
        client.connect().await?; // retry once runtime confirmed healthy
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling connect() on a PolymarketWebSocketClient while the tokio runtime is being torn down, inside a runtime drop, or via tokio::spawn with no reactor (e.g. running async code outside #[tokio::main]/block_on).

Common situations: Graceful shutdown racing with a reconnect attempt; spawning the client in a plain std::thread without a runtime; nested runtime misuse with tokio::spawn.

Related errors


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