nautechsystems/nautilus_trader · error · anyhow::Error
dYdX WebSocket stream task is already registered
Error message
dYdX WebSocket stream task is already registered
What it means
The dYdX execution client expects exactly one WebSocket stream processing task for its lifetime. spawn_ws_stream_handler checks session_tasks before spawning and bails if a stream handler task already exists, preventing duplicate message processing and double-handling of account/order events.
Source
Thrown at crates/adapters/dydx/src/execution/mod.rs:308
self.order_contexts.insert(client_id_u32, context);
}
fn get_order_context(&self, client_id_u32: u32) -> Option<OrderContext> {
self.order_contexts
.get(&client_id_u32)
.map(|r| r.value().clone())
}
fn get_chain_id(&self) -> ChainId {
self.config.get_chain_id()
}
fn spawn_ws_stream_handler(
&self,
stream: impl Stream<Item = DydxWsOutputMessage> + Send + 'static,
) -> anyhow::Result<()> {
if !self.session_tasks.is_empty() {
anyhow::bail!("dYdX WebSocket stream task is already registered");
}
log::debug!("Starting execution WebSocket message processing task");
let trader_id = self.core.trader_id;
let account_id = self.core.account_id;
let instrument_cache = self.instrument_cache.clone();
let oracle_prices = self.oracle_prices.clone();
let encoder = self.encoder.clone();
let order_contexts = self.order_contexts.clone();
let order_id_map = self.order_id_map.clone();
let dispatch_state = self.dispatch_state.clone();
let block_time_monitor = self.block_time_monitor.clone();
let emitter = self.emitter.clone();
let clock = self.clock;
let future = async move {
log::debug!("Execution WebSocket message loop started");View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure connect() is called only once per client instance
- Fully disconnect/tear down (clearing session_tasks) before reconnecting and spawning a new handler
- Create a fresh execution client instance instead of reusing one after shutdown
Example fix
// before client.connect().await?; client.connect().await?; // second spawn fails // after client.connect().await?; // on reconnect: disconnect first, then client.connect().await?;
Defensive patterns
Strategy: validation
Validate before calling
// guard in caller
if client.is_connected() {
return Ok(()); // already connected; do not call connect again
} Prevention
- Call connect() once per client instance; track connected state
- Always disconnect/teardown fully before reconnecting
- On reconnect, create a new client instance rather than reusing a live one
When it happens
Trigger: Calling spawn_ws_stream_handler (typically from connect) when session_tasks is non-empty — i.e. connect() invoked twice on the same client, or reconnect logic spawning a second handler without shutting down the first.
Common situations: Re-connecting after a network drop without tearing down the previous session; a framework lifecycle bug that calls connect more than once on the same client instance.
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
- std::mem::take(&mut self.shutdown_errors).join("; ")
- subscription state lock poisoned
- InstrumentState channel requires kind and currency parameter
- std::mem::take(&mut self.shutdown_errors).join("; ")
- noid '{}' does not match new order oid '{}'
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/cadb1f1daea285a2.
Report an issue: GitHub.