nautechsystems/nautilus_trader · error

Failed to disconnect data clients: {data_err}; failed to dis

Error message

Failed to disconnect data clients: {data_err}; failed to disconnect execution clients: {exec_err}

What it means

Kernel::disconnect_clients shuts down data and execution client connections concurrently via futures::join!. If both disconnect futures fail, neither error is preferred, so both are combined into one message identifying each side. A single failure is returned as-is; only the both-failed case produces this combined error.

Source

Thrown at crates/system/src/kernel.rs:1095

    /// Disconnects all engine clients.
    ///
    /// # Errors
    ///
    /// Returns an error if any client fails to disconnect.
    #[expect(clippy::await_holding_refcell_ref)] // Single-threaded runtime, intentional design
    pub async fn disconnect_clients(&mut self) -> anyhow::Result<()> {
        log::info!("Disconnecting clients...");
        let mut data_engine = self.data_engine.borrow_mut();
        let mut exec_engine = self.exec_engine.borrow_mut();
        let (data_result, exec_result) =
            futures::join!(data_engine.disconnect(), exec_engine.disconnect());

        match (data_result, exec_result) {
            (Ok(()), Ok(())) => Ok(()),
            (Err(data_err), Ok(())) => Err(data_err),
            (Ok(()), Err(exec_err)) => Err(exec_err),
            (Err(data_err), Err(exec_err)) => anyhow::bail!(
                "Failed to disconnect data clients: {data_err}; failed to disconnect execution \
                 clients: {exec_err}"
            ),
        }
    }

    /// Returns `true` if all engine clients are connected.
    #[must_use]
    pub fn check_engines_connected(&self) -> bool {
        self.data_engine.borrow().check_connected() && self.exec_engine.borrow().check_connected()
    }

    /// Returns `true` if all engine clients are disconnected.
    #[must_use]
    pub fn check_engines_disconnected(&self) -> bool {
        self.data_engine.borrow().check_disconnected()
            && self.exec_engine.borrow().check_disconnected()
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect both halves of the message; fix the underlying transport/venue issue for each side separately
  2. If clients are already dead, this may be benign — verify no orders/state were left dirty and continue shutdown
  3. Add retry or timeout handling around disconnect if the venue is intermittently unreachable
  4. Check client logs for whether disconnect errors indicate already-closed connections (often ignorable at teardown)
Defensive patterns

Strategy: retry

Validate before calling

// Probe connectivity before shutdown-sensitive operations
let data_ok = data_engine.ping().is_ok();
let exec_ok = exec_engine.ping().is_ok();
log::info!("disconnect pre-check: data_ok={data_ok} exec_ok={exec_ok}");

Try / catch

match kernel.disconnect_clients().await {
    Err(e) if e.to_string().contains("Failed to disconnect data clients") => {
        // both sides failed; treat as best-effort teardown, log both halves
        log::warn!("disconnect: {e}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling kernel.disconnect_clients() (or node stop/shutdown) when both the data engine's and the execution engine's disconnect() fail — e.g. both venue WebSocket sessions are already dead or their clients error during teardown.

Common situations: Network outage at shutdown so both connections abort with transport errors; venue-side session termination; client cleanup code raising errors for both engines simultaneously.

Related errors


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