nautechsystems/nautilus_trader · error · anyhow::Error
Failed to acquire WebSocket handler task spawner: {e}
Error message
Failed to acquire WebSocket handler task spawner: {e} What it means
In OKX WebSocket `connect`, after starting a new handler generation, the client acquires a spawner used to launch per-stream handler tasks. If `spawner()` fails (task group not in a spawnable state), connect fails with this error before opening the connection.
Source
Thrown at crates/adapters/okx/src/websocket/client.rs:614
let connect_lock = Arc::clone(&self.connect_lock);
let _connect_guard = connect_lock.lock().await;
if !self.handler_tasks.is_empty() && !self.handler_tasks.all_finished() {
anyhow::bail!("Cannot connect while previous WebSocket handler task is still running");
}
if !self.handler_tasks.is_open() || !self.handler_tasks.is_empty() {
self.handler_tasks.begin_shutdown();
self.handler_tasks
.finish_shutdown(Duration::from_secs(2), Duration::from_secs(2))
.await
.map_err(|e| anyhow::anyhow!("Previous WebSocket handler failed: {e}"))?;
self.handler_tasks.start_generation().map_err(|e| {
anyhow::anyhow!("Failed to start WebSocket handler task generation: {e}")
})?;
}
let handler_spawner = self.handler_tasks.spawner().map_err(|e| {
anyhow::anyhow!("Failed to acquire WebSocket handler task spawner: {e}")
})?;
let handler_abort = CancellationToken::new();
*self.handler_abort.lock() = handler_abort.clone();
let mut rollback = ConnectRollback {
handler_tasks: Arc::clone(&self.handler_tasks),
signal: Arc::clone(&self.signal),
handler_abort: handler_abort.clone(),
socket_control: self.socket_control.clone(),
armed: true,
};
// Reset signal so is_active()/is_closed() work after a previous close()
self.signal.store(false, Ordering::Release);
let (message_handler, raw_rx) = channel_message_handler();
// No-op ping handler: handler owns the WebSocketClient and responds to pings directly
// in the message loop for minimal latency (see handler.rs TEXT_PONG response)View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure the client is not closed/shutting down before calling connect.
- Avoid concurrent connect calls on the same client; use a single supervisor task that owns the connection lifecycle.
- Retry connect with backoff; the transient task-group state usually resolves after the prior generation finishes.
Defensive patterns
Strategy: retry
Try / catch
// rust
if let Err(e) = client.connect().await {
if e.to_string().contains("handler task spawner") {
tokio::time::sleep(Duration::from_secs(1)).await;
client.connect().await?;
} else { return Err(e); }
} Prevention
- Keep a single owner task for the connection lifecycle.
- Check the client is not closed before connecting.
- Back off between reconnect attempts.
When it happens
Trigger: connect called when `handler_tasks.spawner()` returns Err — the task group was not successfully started (e.g. generation start failed silently, group is closed/shutting down, or acquired concurrently by another connect).
Common situations: Reconnect storms sharing a single client instance; calling connect on a client whose close() has already begun; race between connect and disconnect on different tasks.
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
- Failed to start WebSocket handler task generation: {e}
- Invalid `BarSpecification` for channel, was {bar_spec}
- errors.join("; ")
- Timeout waiting for account {account_id} to be registered af
- `api_key`, `api_secret`, `api_passphrase` credentials must b
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/826bdd1a9a2d611f.
Report an issue: GitHub.