{"record":{"id":"80086c7fd33443e9","repo":"nautechsystems/nautilus_trader","slug":"hyperliquid-allows-at-most-websocket-connection","errorCode":null,"errorMessage":"Hyperliquid allows at most {} WebSocket connections per route","messagePattern":"Hyperliquid allows at most (.+?) WebSocket connections per route","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/hyperliquid/src/websocket/client.rs","lineNumber":284,"sourceCode":"        let _guard = connect_lock.lock().await;\n        self.connect_locked().await\n    }\n\n    async fn connect_locked(&mut self) -> anyhow::Result<()> {\n        if self.is_active() {\n            log::warn!(\"WebSocket already connected\");\n            return Ok(());\n        }\n\n        if !self.task_handle.is_empty() {\n            self.disconnect_locked().await?;\n        }\n\n        if self.connection_permit.lock().is_none() {\n            let permit = Arc::clone(&self.rate_limits.connection_slots)\n                .try_acquire_owned()\n                .map_err(|_| {\n                    anyhow::anyhow!(\n                        \"Hyperliquid allows at most {} WebSocket connections per route\",\n                        crate::common::consts::HYPERLIQUID_WS_CONNECTIONS_MAX,\n                    )\n                })?;\n            *self.connection_permit.lock() = Some(permit);\n        }\n\n        // A fresh socket has no venue-side subscriptions; stale book stream\n        // entries must not gate the venue subscribe for re-subscriptions\n        self.book_streams.clear();\n\n        let (message_handler, raw_rx) = channel_message_handler();\n        let cfg = WebSocketConfig {\n            url: self.url.clone(),\n            headers: vec![],\n            heartbeat_interval_secs: None,\n            heartbeat_payload: None,\n            connect_timeout_ms: Some(15_000),","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/hyperliquid/src/websocket/client.rs#L266-L302","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Share a single WebSocket client and multiplex subscriptions instead of creating one client per instrument","Ensure unused clients are closed/dropped so their permits are released; look for leaked clients holding the semaphore","Raise HYPERLIQUID_WS_CONNECTIONS_MAX (crate::common::consts) if the venue allows more connections for your use case","Retry the connect with backoff after other connections close (the permit is a try_acquire, not a wait)"],"exampleFix":"// before\nfor instrument in instruments {\n    let client = WebSocketClient::new(...);  // one client per instrument -> exhausts permits\n    client.connect().await?;\n}\n// after\nlet client = WebSocketClient::new(...);  // single shared client\nclient.connect().await?;\nfor instrument in instruments {\n    client.subscribe_book(...).await?;    // multiplex subscriptions on one connection\n}","handlingStrategy":"retry","validationCode":"# Python/Rust: count active clients before opening another connection\nassert active_ws_clients() < HYPERLIQUID_WS_CONNECTIONS_MAX, \"too many WebSocket connections\"","typeGuard":null,"tryCatchPattern":"// Rust\nloop {\n    match client.connect().await {\n        Ok(()) => break,\n        Err(e) if e.to_string().contains(\"WebSocket connections per route\") => {\n            tokio::time::sleep(Duration::from_secs(2)).await; // backoff, retry\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Share one WebSocket client across many instrument subscriptions","Always close/drop clients you no longer use so semaphore permits are released","Audit for leaked clients after reconfiguration or reconnect logic","Keep concurrent processes/tests within the per-route connection cap"],"tags":["rust","hyperliquid","websocket","connection-limit","rate-limit"],"backgroundTag":"connection-limit-exceeded","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}