nautechsystems/nautilus_trader · error · anyhow::Error

Binance Futures dispatch task did not stop after abort

Error message

Binance Futures dispatch task did not stop after abort

What it means

await_dispatch_task aborted the dispatch task and waited on its join handle; a TaskJoinOutcome::Incomplete means the task neither completed nor acknowledged the abort within the allowed window. This indicates a wedged dispatch loop that ignored cancellation during connect/disconnect.

Source

Thrown at crates/adapters/binance/src/futures/execution.rs:947

    async fn await_dispatch_task(&self) -> anyhow::Result<()> {
        let _recovery_guard = self.recovery_lock.lock().await;
        let mut task_slot = self.ws_task.lock().await;
        let Some(outcome) = finish_task(
            &mut task_slot,
            Duration::from_secs(1),
            Duration::from_secs(2),
        )
        .await
        else {
            return Ok(());
        };

        match outcome {
            TaskJoinOutcome::Completed(()) | TaskJoinOutcome::Aborted => Ok(()),
            TaskJoinOutcome::Failed(e) => {
                Err(anyhow::anyhow!("Binance Futures dispatch task failed: {e}"))
            }
            TaskJoinOutcome::Incomplete => Err(anyhow::anyhow!(
                "Binance Futures dispatch task did not stop after abort"
            )),
        }
    }

    async fn close_listen_key_slot(
        &self,
        slot: &RwLock<Option<SecretString>>,
        context: &str,
    ) -> anyhow::Result<()> {
        let key = slot.read().clone();
        let Some(key) = key else {
            return Ok(());
        };

        self.http_client
            .close_listen_key(key.expose_secret())
            .await

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Retry the disconnect after the runtime settles
  2. Check for blocking calls or long-held locks in any custom handlers feeding the dispatch loop
  3. Ensure the tokio runtime has adequate worker threads and is not starved
  4. Upgrade to the latest nautilus_trader version; report a bug if it reproduces, as this indicates a cancellation-safety issue

Example fix

// before
client.disconnect().await?; // may hang/fail if dispatch task is wedged
// after
if let Err(e) = client.disconnect().await {
    tracing::warn!("disconnect incomplete: {e:#}");
    tokio::time::sleep(Duration::from_secs(1)).await;
    client.disconnect().await?;
}
Defensive patterns

Strategy: retry

Try / catch

if let Err(e) = client.disconnect().await {
    if e.to_string().contains("did not stop after abort") {
        tokio::time::sleep(Duration::from_secs(1)).await;
        client.disconnect().await?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling connect or disconnect when the dispatch task is blocked in a non-cancellation-safe operation (e.g. a lock held across await, a blocking call in the async loop) and does not finish after abort is requested.

Common situations: Deadlocks in runtime under heavy load, blocking I/O inside the async dispatch loop, or tokio runtime starvation preventing the aborted task from being polled to completion.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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