nautechsystems/nautilus_trader · error

RTDS WebSocket client unavailable after reconcile

Error message

RTDS WebSocket client unavailable after reconcile

What it means

Raised after a reconcile attempts to (re)establish the RTDS WebSocket: ensure_connected_locked returned success but current_ws() still yields no client. This is an internal invariant violation — the code expected ensure_connected_locked to install a WebSocket client whenever subscriptions exist.

Source

Thrown at crates/adapters/polymarket/src/rtds.rs:833

                }
            }
        }
    }

    async fn reconcile_once(&self, reset_live_state: bool) -> anyhow::Result<()> {
        let _guard = self.inner.wire_mutex.lock().await;

        if self.inner.closing.load(Ordering::Acquire) {
            return Ok(());
        }

        if !self.has_subscriptions() && self.current_ws().is_none() {
            return Ok(());
        }

        let fresh_connect = self.ensure_connected_locked().await?;
        let Some(ws) = self.current_ws() else {
            anyhow::bail!("RTDS WebSocket client unavailable after reconcile");
        };

        self.reconcile_live_locked(&ws, fresh_connect || reset_live_state)
            .await
    }

    async fn ensure_connected_locked(&self) -> anyhow::Result<bool> {
        let generation = {
            let generation = self.inner.shutdown_generation.lock();

            if self.inner.closing.load(Ordering::Acquire) {
                return Ok(false);
            }
            *generation
        };

        if self.current_ws().is_some_and(|ws| !ws.is_disconnected()) {
            return Ok(false);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure ensure_connected_locked always either installs a client or returns an error; propagate shutdown bails instead of swallowing them.
  2. Hold the state lock across connect and the ws lookup so shutdown cannot clear the client in between.
  3. Re-check the shutdown/generation state after ensure_connected_locked and return a cancellation error instead.
  4. If it persists, file a bug: this indicates a real invariant break in the feed's connect logic.

Example fix

// before
let fresh_connect = self.ensure_connected_locked().await?;
let Some(ws) = self.current_ws() else {
    anyhow::bail!("RTDS WebSocket client unavailable after reconcile");
};
// after
let fresh_connect = self.ensure_connected_locked().await?;
if !self.is_generation_open(generation) {
    anyhow::bail!("RTDS reconcile was canceled by shutdown");
}
let Some(ws) = self.current_ws() else {
    anyhow::bail!("RTDS WebSocket client unavailable after reconcile");
};
Defensive patterns

Strategy: retry

Validate before calling

// before reconcile
if !feed.has_subscriptions() && !feed.has_ws_client() {
    return Ok(());
}
if feed.is_shutdown() {
    return Ok(());
}

Try / catch

if let Err(e) = feed.reconcile().await {
    if e.to_string().contains("unavailable after reconcile") {
        log::error!("RTDS reconcile invariant violated: {e:#}");
        // recreate the feed instance
    }
    return Err(e);
}

Prevention

When it happens

Trigger: ensure_connected_locked returns Ok without installing a client while has_subscriptions() is true and no WebSocket client is stored — e.g. a swallowed shutdown bail in the connect path, or a race where the installed client was cleared between connect and lookup.

Common situations: Concurrent shutdown clearing ws_client while reconcile runs; subscriptions added just as the feed is being torn down; a modified connect path that returns Ok without storing the client.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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