nautechsystems/nautilus_trader · error

Architect AX orders WebSocket handler did not stop after abo

Error message

Architect AX orders WebSocket handler did not stop after abort

What it means

Task-teardown failure in the Architect AX orders WebSocket client's close: after signaling abort and joining the handler task, the join outcome was Failed, meaning the background handler itself errored while shutting down rather than completing or being aborted cleanly.

Source

Thrown at crates/adapters/architect_ax/src/websocket/orders/client.rs:754

        tokio::time::sleep(Duration::from_millis(50)).await;
        self.signal.store(true, Ordering::Release);

        let outcome = self
            .task_handle
            .finish(Duration::from_secs(2), Duration::from_secs(2))
            .await;
        *self.reconnect_headers.lock() = None;

        if let Some(control) = &self.socket_control {
            control.deregister();
        }

        match outcome {
            None | Some(TaskJoinOutcome::Completed(()) | TaskJoinOutcome::Aborted) => Ok(()),
            Some(TaskJoinOutcome::Failed(error)) => Err(anyhow::anyhow!(
                "Architect AX orders WebSocket handler failed: {error}"
            )),
            Some(TaskJoinOutcome::Incomplete) => Err(anyhow::anyhow!(
                "Architect AX orders WebSocket handler did not stop after abort"
            )),
        }
    }

    async fn send_cmd(&self, cmd: HandlerCommand) -> AxOrdersWsResult<()> {
        let guard = self.cmd_tx.read().await;
        guard
            .send(cmd)
            .map_err(|e| AxOrdersWsClientError::ChannelError(e.to_string()))
    }
}

impl Drop for AxOrdersWebSocketClient {
    fn drop(&mut self) {
        if Arc::strong_count(&self.task_handle) == 1 && !self.task_handle.is_empty() {
            self.cancellation_token.load().cancel();
            self.signal.store(true, Ordering::Release);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Audit the orders handler loop for cancellation-token/abort handling around blocking awaits
  2. Add tokio::select! with the abort signal in long-running waits inside the handler
  3. Check for deadlock between command senders and the handler
  4. If reproducible, file a bug: a leaked handler task can hold the runtime or socket open
Defensive patterns

Strategy: try-catch

Try / catch

// Rust
if let Err(e) = orders.close().await {
    tracing::error!("orders ws did not stop: {e}");
    // consider aborting the underlying task handle or restarting the client
}

Prevention

When it happens

Trigger: Calling close() on the orders client while the orders handler is stuck (blocked await, non-cancelling loop, deadlock) and never reaches completion, failure, or abort-acknowledgement.

Common situations: Handler hung waiting on the exchange socket without honoring cancellation; command channel deadlock; very slow remote endpoint during shutdown.

Related errors


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