nautechsystems/nautilus_trader · warning

Lighter WebSocket initial connection cancelled

Error message

Lighter WebSocket initial connection cancelled

What it means

Raised when the initial Lighter WebSocket connection succeeded, but by the time it finished the cancellation token was cancelled or the connection generation advanced (a newer connect attempt superseded this one). The client is disconnected and the stale attempt is aborted with this error instead of returning a connection that nobody owns.

Source

Thrown at crates/adapters/lighter/src/websocket/client.rs:498

                    .map(SocketControl::sink)
                    .or_else(|| self.socket_sink.clone()),
            )
            .connect();
        let client =
            match tokio::time::timeout(Duration::from_secs(self.ws_timeout_secs), connect).await {
                Ok(result) => result?,
                Err(_) => anyhow::bail!(
                    "Lighter WebSocket initial connection timeout after {} seconds",
                    self.ws_timeout_secs,
                ),
            };

        if cancellation_token.is_cancelled()
            || generation != self.connection_generation.load(Ordering::Acquire)
        {
            client.disconnect().await;

            anyhow::bail!("Lighter WebSocket initial connection cancelled");
        }

        let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel::<HandlerCommand>();
        let (out_tx, out_rx) = tokio::sync::mpsc::unbounded_channel::<NautilusWsMessage>();

        // Capture the connection-mode atomic before moving `client` into the
        // SetClient command below.
        let connection_mode_atomic = client.connection_mode_atomic();
        let connection_epoch_atomic = client.connection_epoch_atomic();

        // Queue SetClient (and the instrument cache replay) onto the new
        // command channel BEFORE publishing it to clones or marking the
        // connection active. Otherwise a clone observing `is_active()` could
        // race in and send a Subscribe before SetClient lands, and the
        // handler would drop the subscription because `inner == None`.
        let reconnect_handle = client.reconnect_handle();
        if let Err(e) = cmd_tx.send(HandlerCommand::SetClient(client)) {
            anyhow::bail!("Failed to send SetClient command: {e}");

View on GitHub (pinned to 18893faf8b)

Solutions

  1. If intentional shutdown: this is expected; handle the error as a benign cancellation rather than a fault.
  2. Avoid concurrent connect() calls on the same client; await the first connect before triggering a reconnect.
  3. Check whether something cancels the token early (e.g. a shutdown signal firing during startup) and fix the shutdown ordering.
  4. If reconnect storms occur, add backoff between reconnect attempts so generations don't race.
  5. Upgrade the adapter if a known race in generation handling causes spurious cancellations.

Example fix

// before: cancelling while connect is in flight
let handle = tokio::spawn(client.connect());
token.cancel(); // connect will bail: initial connection cancelled
let client = handle.await??;

// after: connect first, then allow cancellation
client.connect().await?;
token.cancel(); // now shutdown is orderly
Defensive patterns

Strategy: try-catch

Try / catch

match client.connect().await {
    Err(e) if e.to_string().contains("initial connection cancelled") => {
        log::info!("connect superseded or cancelled; treating as benign");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling connect() and cancelling the CancellationToken while the handshake is in flight, or calling connect()/reconnect concurrently such that connection_generation increments before the first attempt finishes — the losing attempt bails here.

Common situations: Shutting down a node or data engine while the WebSocket client is still connecting; rapid disconnect/reconnect cycles (e.g. stream URL hot-reload) racing an in-progress connect; a caller dropping the connect future and retrying.

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/24c726e1db40fced. Report an issue: GitHub.