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

  1. Retry connect after a short backoff; the stuck generation usually ends once the underlying socket times out.
  2. Fully disconnect/close the previous connection before reconnecting instead of calling connect over a live session.
  3. 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

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


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