nautechsystems/nautilus_trader · error · anyhow::Error
Failed to start Hyperliquid WebSocket handler task: {e}
Error message
Failed to start Hyperliquid WebSocket handler task: {e} What it means
Raised in connect_locked when tokio::spawn of the WebSocket handler task fails. The spawn failure means the tokio runtime refused to start the task, most commonly because the runtime is shutting down, so no handler exists to process commands or read the WebSocket and the connection attempt is aborted. The client resets out_rx and releases reserved rate-limiter slots before returning.
Source
Thrown at crates/adapters/hyperliquid/src/websocket/client.rs:507
}
}
None => {
if handler.is_stopped() {
log::debug!("Stop signal received, ending message processing");
break;
}
log::warn!("WebSocket stream ended unexpectedly");
break;
}
}
}
rate_limits.release_client(client_id);
connection_permit.lock().take();
log::debug!("Handler task completed");
}) {
self.out_rx = None;
self.release_limit_reservations();
anyhow::bail!("Failed to start Hyperliquid WebSocket handler task: {e}");
}
Ok(())
}
pub fn set_post_timeout(&mut self, timeout: Duration) {
self.post_timeout = timeout;
}
pub(crate) fn begin_shutdown(&self) {
self.signal.store(true, Ordering::Relaxed);
}
/// Replaces state owned by a terminated WebSocket generation.
///
/// This must run only after the handler task has stopped. Replacing the
/// shared containers, rather than clearing them, prevents old clones or
/// in-flight work from mutating a subsequent connection generation.
pub(crate) fn reset_runtime_state(&mut self) {View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure connect() is only awaited while the tokio runtime is alive; move reconnection logic into a normally spawned task.
- Check whether the process/runtime is shutting down and stop reconnect attempts when a shutdown signal is observed.
- Keep the Runtime/Handle alive for the client's whole lifetime and shut it down only after dropping clients.
- Retry connect() on a live runtime if the failure was transient shutdown overlap.
Example fix
// before: connect from Drop while runtime shuts down / impl Drop for MyService { fn drop(&mut self) { rt.block_on(client.connect()); } } / // after: connect inside a live runtime task / async fn run(service: MyService) -> anyhow::Result<()> { service.client.connect().await?; service.run().await } Defensive patterns
Strategy: retry
Validate before calling
fn can_connect(shutdown_requested: bool) -> bool { !shutdown_requested } Try / catch
match client.connect().await { Err(e) if e.to_string().contains("handler task") && shutdown_requested() => { log::info!("skipping reconnect: runtime shutting down"); } Err(e) => return Err(e.into()), Ok(()) => {} } Prevention
- Never call connect() from Drop impls or shutdown hooks.
- Hold the tokio Runtime/Handle for the client's entire lifetime.
- Observe shutdown signals and cancel reconnect loops before teardown.
- Keep reconnect logic inside a normally spawned task on the live runtime.
When it happens
Trigger: Calling connect() (via connect_locked) while the enclosing tokio runtime is shutting down or has already been dropped, so tokio::spawn cannot register the new handler task.
Common situations: Calling connect() from a Drop impl or shutdown hook; a reconnect loop still running while the runtime tears down; dropping the Runtime/Handle while the client lives; blocking a runtime until shutdown while retries continue.
Related errors
- Failed to start Coinbase WebSocket handler task: {e}
- failed to register WebSocket handler task: {e}
- Hyperliquid WebSocket handler failed: {error}
- Hyperliquid WebSocket handler did not stop after abort
- Failed to start Lighter WebSocket handler task: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/67e21d41a6d2ce16.
Report an issue: GitHub.