nautechsystems/nautilus_trader · error · anyhow::Error
Previous WebSocket handler failed: {e}
Error message
Previous WebSocket handler failed: {e} What it means
During OKX WebSocket `connect`, any lingering handler tasks from a previous connection are shut down with deadlines. If `finish_shutdown` fails or times out (a handler task hung and did not end within 2s), the connect fails with this error rather than starting a new generation on top of stuck tasks.
Source
Thrown at crates/adapters/okx/src/websocket/client.rs:608
/// Connect to the OKX WebSocket server.
///
/// # 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()View on GitHub (pinned to 18893faf8b)
Solutions
- Retry connect after a short backoff; the stuck generation usually ends once the underlying socket times out.
- Fully disconnect/close the previous connection before reconnecting instead of calling connect over a live session.
- Increase patience between reconnect attempts so handler tasks are not re-entered mid-shutdown.
Defensive patterns
Strategy: retry
Try / catch
// rust
match client.connect().await {
Err(e) if e.to_string().contains("Previous WebSocket handler failed") => {
tokio::time::sleep(Duration::from_secs(2)).await;
client.connect().await?;
}
other => other?,
} Prevention
- Disconnect fully before reconnecting.
- Use a single reconnect supervisor instead of calling connect from multiple tasks.
- Apply exponential backoff in reconnect loops.
When it happens
Trigger: Calling connect (public or private) while a previous connection's handler tasks are still open and one of them fails to terminate within the 2-second shutdown deadline, or the task group reports an internal shutdown error.
Common situations: Rapid reconnect loops after network drops; a handler task blocked on an unresponsive send while the socket is half-dead; calling connect twice without awaiting prior teardown.
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
- Cumulative spread fill went backwards: acc_fill_sz='{}' < pr
- Cumulative fill went backwards: acc_fill_sz='{acc_fill_sz}'
- Timed out joining WebSocket handler task after abort: {error
- Invalid `BarSpecification` for channel, was {bar_spec}
- errors.join("; ")
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/69146c6c838b00ab.
Report an issue: GitHub.