nautechsystems/nautilus_trader · error

Failed to start Lighter WebSocket handler task: {e}

Error message

Failed to start Lighter WebSocket handler task: {e}

What it means

Raised when spawning the Lighter WebSocket handler tokio task fails. The code resets self.out_rx to None and bails with the JoinHandle error from tokio::spawn. On modern tokio this almost always means the runtime is shutting down or spawn was called outside a runtime context.

Source

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

                                log::error!("Failed to send Lighter message (receiver dropped)");
                            }
                            break;
                        }
                    }
                    None => {
                        if handler.is_stopped() {
                            log::debug!("Lighter handler stop signal observed, exiting loop");
                            break;
                        }
                        log::warn!("Lighter WebSocket stream ended unexpectedly");
                        break;
                    }
                }
            }
            log::debug!("Lighter handler task completed");
        }) {
            self.out_rx = None;
            anyhow::bail!("Failed to start Lighter WebSocket handler task: {e}");
        }
        Ok(())
    }

    /// Disconnects gracefully: signals shutdown, drains the handler, then
    /// awaits the task handle with a timeout.
    ///
    /// # Errors
    ///
    /// This function currently completes best-effort shutdown and returns `Ok(())`.
    pub async fn disconnect(&mut self) -> Result<(), LighterWsError> {
        self.connection_generation.fetch_add(1, Ordering::AcqRel);
        self.initial_connect_cancellation.load().cancel();

        let _guard = self.connection_lock.lock().await;
        self.initial_connect_cancellation.load().cancel();

        log::debug!("Disconnecting Lighter WebSocket");

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure connect() is awaited within a live tokio runtime (use #[tokio::main] or an explicit Runtime/Handle).
  2. If using a custom runtime, pass its Handle so tokio::spawn can find an executor.
  3. Delay client startup until after the runtime is initialized; don't spawn clients during shutdown.
  4. Check that node start ordering doesn't stop the runtime before WebSocket clients connect.
  5. Wrap startup in a runtime::Handle::current().spawn if bridging from another executor.

Example fix

// before: calling connect outside a runtime
std::thread::spawn(|| client.connect().await?); // spawn fails

// after: run inside the tokio runtime
let handle = tokio::runtime::Handle::current();
handle.spawn(async move { client.connect().await })?;
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure a runtime exists before connecting
let _guard = tokio::runtime::Handle::try_current()?;

Try / catch

match client.connect().await {
    Err(e) if e.to_string().contains("Failed to start") => {
        log::error!("runtime unavailable for handler spawn: {e:#}");
        // re-run startup inside an active runtime handle
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling connect() outside a tokio runtime, or during runtime shutdown, so tokio::spawn for the handler task returns an error which is surfaced with this message.

Common situations: Calling client.connect() from a blocking thread or plain async fn without #[tokio::main]/runtime handle; stopping the actor/runtime while a data client is still initializing; embedding the adapter in a non-tokio executor.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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