nautechsystems/nautilus_trader · error

IB data startup teardown failed: {teardown_error}

Error message

IB data startup teardown failed: {teardown_error}

What it means

During InteractiveBrokersDataClient::connect, if the background task that monitors IB data farm notices fails to spawn, the client tears down its session/command task groups and drops the IB client handle. If that teardown (finish_tasks) itself errors, the original spawn error is re-raised with the teardown error appended, so one failure masks another.

Source

Thrown at crates/adapters/interactive_brokers/src/data/core.rs:749

        self.ib_client = Some(handle);

        let data_farm_state = Arc::clone(&self.data_farm_state);
        let cancellation_token = self.cancellation_token.child_token();
        let clock = self.clock;

        if let Err(e) = self.session_tasks.spawn(async move {
            if let Err(e) =
                monitor_data_farm_notices(client, data_farm_state, clock, cancellation_token).await
            {
                tracing::warn!("IB data farm notice monitor stopped: {e:?}");
            }
        }) {
            self.session_tasks.begin_shutdown();
            self.command_tasks.begin_shutdown();
            self.ib_client = None;

            if let Err(teardown_error) = self.finish_tasks().await {
                return Err(anyhow::Error::new(e)
                    .context(format!("IB data startup teardown failed: {teardown_error}")));
            }
            return Err(anyhow::Error::new(e).context("failed to register IB data farm monitor"));
        }
        self.is_connected.store(true, Ordering::Relaxed);

        let instrument_count = self.instrument_provider.count();
        if instrument_count > 0 {
            tracing::debug!(
                "Data client connected with {} instruments in provider cache",
                instrument_count
            );

            for instrument in self.instrument_provider.get_all() {
                if let Err(e) = self.data_sender.send(DataEvent::Instrument(instrument)) {
                    tracing::warn!("Failed to publish startup-loaded instrument: {e}");
                    break;
                }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the teardown_error in the message to see why task cleanup failed (e.g. runtime already dropped); fix that root cause first.
  2. Ensure connect() is only called once per client instance; create a fresh client after any failed connect.
  3. Call disconnect() before re-connecting so session/command task groups are cleanly re-initialized.
  4. Verify the tokio runtime backing the client is alive and not being shut down while connect() is awaited.

Example fix

// before
let client = IBDataClient::new(...); client.connect().await?; client.connect().await?; // second connect fails
// after
if !client.is_connected() { client.connect().await?; }
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: guard against double-connect / dead runtime
if client_is_connected { return Ok(()); }
if tokio::runtime::Handle::try_current().is_err() { return Err(anyhow!("no active runtime")); }

Type guard

fn is_connected(client: &IBDataClient) -> bool { client.is_connected() }

Try / catch

match client.connect().await {
    Err(e) if e.to_string().contains("teardown failed") => {
        // inspect chained teardown_error; rebuild a fresh client
        eprintln!("connect failed: {e:#}");
    }
    r => r?,
}

Prevention

When it happens

Trigger: connect() succeeds against IB Gateway/TWS but self.session_tasks.spawn(...) returns Err (task-group already shut down or spawn capacity exhausted); then finish_tasks() during rollback also returns Err.

Common situations: Calling connect() twice without disconnect(), reusing the client after a prior failed connect left the task groups in a shutdown state, or a client whose runtime was dropped mid-connect.

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


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