nautechsystems/nautilus_trader · error · anyhow::Error
Failed to start WebSocket handler task generation: {e}
Error message
Failed to start WebSocket handler task generation: {e} What it means
After shutting down previous OKX WebSocket handler tasks, `connect` starts a new task generation in the handler task group. If `start_generation()` returns an error (task group in a bad state, e.g. still draining or already shutting down), connect aborts with this message.
Source
Thrown at crates/adapters/okx/src/websocket/client.rs:610
/// # Errors
///
/// Returns an error if the connection process fails.
pub async fn connect(&mut self) -> anyhow::Result<()> {
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);
View on GitHub (pinned to 18893faf8b)
Solutions
- Serialize connection lifecycle: guard connect/disconnect behind a mutex or ensure only one reconnect driver exists.
- Retry connect after the previous shutdown completes (backoff and re-attempt).
- Inspect the inner `{e}` to identify whether the task group is draining/shutdown and wait for that state to clear.
Defensive patterns
Strategy: retry
Try / catch
// rust
for attempt in 0..3 {
match client.connect().await {
Ok(()) => break,
Err(e) if e.to_string().contains("handler task generation") => {
tokio::time::sleep(Duration::from_millis(500 * (attempt + 1))).await;
}
Err(e) => return Err(e),
}
} Prevention
- Never share one client across concurrent connect calls.
- Await disconnect completion before issuing a new connect.
- Avoid connecting during client shutdown.
When it happens
Trigger: connect is called while the handler task group cannot begin a new generation — typically immediately after a failed shutdown of the prior generation or concurrent connect calls on the same client.
Common situations: Concurrent `connect()` calls from multiple threads/tasks sharing one client; reconnect logic racing with an in-progress disconnect; calling connect during client shutdown.
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 acquire WebSocket handler task spawner: {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/a131dd89c1e177ec.
Report an issue: GitHub.