nautechsystems/nautilus_trader · error · anyhow::Error

Hyperliquid allows at most {} WebSocket connections per rout

Error message

Hyperliquid allows at most {} WebSocket connections per route

What it means

Raised in WebSocketClient::connect_locked when acquiring a semaphore permit from connection_slots fails via try_acquire_owned, meaning all slots for the route are held. Hyperliquid enforces a cap of HYPERLIQUID_WS_CONNECTIONS_MAX simultaneous WebSocket connections per route to respect venue rate limits. The error reports the configured maximum so operators can size their connection usage.

Source

Thrown at crates/adapters/hyperliquid/src/websocket/client.rs:284

        let _guard = connect_lock.lock().await;
        self.connect_locked().await
    }

    async fn connect_locked(&mut self) -> anyhow::Result<()> {
        if self.is_active() {
            log::warn!("WebSocket already connected");
            return Ok(());
        }

        if !self.task_handle.is_empty() {
            self.disconnect_locked().await?;
        }

        if self.connection_permit.lock().is_none() {
            let permit = Arc::clone(&self.rate_limits.connection_slots)
                .try_acquire_owned()
                .map_err(|_| {
                    anyhow::anyhow!(
                        "Hyperliquid allows at most {} WebSocket connections per route",
                        crate::common::consts::HYPERLIQUID_WS_CONNECTIONS_MAX,
                    )
                })?;
            *self.connection_permit.lock() = Some(permit);
        }

        // A fresh socket has no venue-side subscriptions; stale book stream
        // entries must not gate the venue subscribe for re-subscriptions
        self.book_streams.clear();

        let (message_handler, raw_rx) = channel_message_handler();
        let cfg = WebSocketConfig {
            url: self.url.clone(),
            headers: vec![],
            heartbeat_interval_secs: None,
            heartbeat_payload: None,
            connect_timeout_ms: Some(15_000),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Share a single WebSocket client and multiplex subscriptions instead of creating one client per instrument
  2. Ensure unused clients are closed/dropped so their permits are released; look for leaked clients holding the semaphore
  3. Raise HYPERLIQUID_WS_CONNECTIONS_MAX (crate::common::consts) if the venue allows more connections for your use case
  4. Retry the connect with backoff after other connections close (the permit is a try_acquire, not a wait)

Example fix

// before
for instrument in instruments {
    let client = WebSocketClient::new(...);  // one client per instrument -> exhausts permits
    client.connect().await?;
}
// after
let client = WebSocketClient::new(...);  // single shared client
client.connect().await?;
for instrument in instruments {
    client.subscribe_book(...).await?;    // multiplex subscriptions on one connection
}
Defensive patterns

Strategy: retry

Validate before calling

# Python/Rust: count active clients before opening another connection
assert active_ws_clients() < HYPERLIQUID_WS_CONNECTIONS_MAX, "too many WebSocket connections"

Try / catch

// Rust
loop {
    match client.connect().await {
        Ok(()) => break,
        Err(e) if e.to_string().contains("WebSocket connections per route") => {
            tokio::time::sleep(Duration::from_secs(2)).await; // backoff, retry
        }
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Opening a new WebSocket connection when HYPERLIQUID_WS_CONNECTIONS_MAX permits are already held — e.g. many instruments/subscriptions each using separate clients, leaked clients that connected but were never dropped/closed, or concurrent connects across the same route exceeding the cap.

Common situations: Spawning many data clients (or one per instrument) instead of sharing one client with multiple subscriptions; failing to close/drop old clients after reconfiguration so permits leak; parallel test suites or multiple processes hitting the same limit cumulatively.

Related errors


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