nautechsystems/nautilus_trader · error

Bybit execution shutdown failed

Error message

Bybit execution shutdown failed: {}

What it means

Aggregated error raised when the Bybit execution client fails to cleanly tear down during connect/disconnect. Individual shutdown step failures are collected in shutdown_errors and joined into a single bail after set_disconnected().

Solutions

  1. Inspect the joined error message to identify which teardown step failed
  2. Check network connectivity and Bybit API status before reconnecting
  3. Retry disconnect(); the client should still be marked disconnected so a fresh connect is possible
  4. Enable debug logging on the Bybit execution client to see per-step shutdown failures
Defensive patterns

Strategy: retry

Try / catch

match exec_client.disconnect().await {
    Err(e) if e.to_string().starts_with("Bybit execution shutdown failed") => {
        tracing::warn!("shutdown partial failure: {e}; forcing reconnect");
        // client is already marked disconnected; proceed with a fresh connect
        exec_client.connect().await?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling connect() or disconnect() when teardown steps fail (e.g. WebSocket close errors, HTTP unsubscribe/cancel failures recorded during teardown).

Common situations: Network partitions during disconnect; venue returning errors on final REST calls; stale WebSocket connections that cannot be closed gracefully.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at crates/adapters/bybit/src/execution.rs:315

            self.shutdown_errors
                .push(format!("trade WebSocket shutdown failed: {e}"));
        }
        self.dispatch_state.clear_repay_sender();

        if let Err(e) = self.await_session_tasks().await {
            self.shutdown_errors.push(e.to_string());
        }

        if let Err(e) = self.await_pending_tasks().await {
            self.shutdown_errors.push(e.to_string());
        }
        self.core.set_disconnected();

        if self.shutdown_errors.is_empty() {
            Ok(())
        } else {
            let errors = std::mem::take(&mut self.shutdown_errors);
            anyhow::bail!("Bybit execution shutdown failed: {}", errors.join("; "))
        }
    }

    /// Polls the cache until the account is registered or timeout is reached.
    async fn await_account_registered(&self, timeout_secs: f64) -> anyhow::Result<()> {
        let account_id = self.core.account_id;

        if self.core.cache().account(&account_id).is_some() {
            log::info!("Account {account_id} registered");
            return Ok(());
        }

        let start = Instant::now();
        let timeout = Duration::from_secs_f64(timeout_secs);
        let interval = Duration::from_millis(10);

        loop {
            tokio::time::sleep(interval).await;

View on GitHub (pinned to 18893faf8b)