nautechsystems/nautilus_trader · error
Coinbase WebSocket handler did not stop after abort
Error message
Coinbase WebSocket handler did not stop after abort
What it means
When closing/reconnecting, connect() aborts the previous handler task and expects the task join to report Completed or Aborted. If the outcome is Incomplete (task neither finished nor confirmed aborted), the adapter treats this as an internal lifecycle violation and bails.
Source
Thrown at crates/adapters/coinbase/src/websocket/client.rs:241
if self.is_active() || self.is_reconnecting() {
log::warn!("WebSocket already connected or reconnecting");
return Ok(());
}
if let Some(outcome) = finish_task(
&mut self.task_handle,
WS_DISCONNECT_TIMEOUT,
WS_DISCONNECT_TIMEOUT,
)
.await
{
match outcome {
TaskJoinOutcome::Completed(()) | TaskJoinOutcome::Aborted => {}
TaskJoinOutcome::Failed(error) => {
anyhow::bail!("Coinbase WebSocket handler failed: {error}");
}
TaskJoinOutcome::Incomplete => {
anyhow::bail!("Coinbase WebSocket handler did not stop after abort");
}
}
}
// Clear stop signal from any previous disconnect
self.signal.store(false, Ordering::Relaxed);
let (message_handler, raw_rx) = channel_message_handler();
let cfg = WebSocketConfig {
url: self.url.clone(),
headers: vec![],
// Coinbase uses TCP control-frame pings for transport keep-alive;
// application-layer liveness comes from the heartbeats channel.
heartbeat_interval_secs: Some(WS_HEARTBEAT_SECS),
heartbeat_payload: None,
connect_timeout_ms: Some(RECONNECT_TIMEOUT.as_millis() as u64),
reconnect_delay_initial_ms: Some(RECONNECT_BASE_BACKOFF.as_millis() as u64),
reconnect_delay_max_ms: Some(RECONNECT_MAX_BACKOFF.as_millis() as u64),View on GitHub (pinned to 18893faf8b)
Solutions
- Avoid concurrent connect()/disconnect() calls; serialize connection lifecycle on one task
- Retry the connect — this is usually a transient lifecycle race
- If persistent, inspect the handler loop to ensure it selects on the stop signal and report/patch the adapter
Defensive patterns
Strategy: retry
Try / catch
for attempt in 0..3 {
match ws.connect().await {
Ok(_) => break,
Err(e) if e.to_string().contains("did not stop after abort") => {
tokio::time::sleep(Duration::from_millis(200 * (attempt + 1))).await;
}
Err(e) => return Err(e),
}
} Prevention
- Never call connect()/disconnect() concurrently from multiple tasks
- Own the WebSocket lifecycle in a single supervisor task
- Avoid rapid disconnect/reconnect loops
When it happens
Trigger: The handler task ignored the abort signal and the join result came back Incomplete — typically a stuck handler loop not honoring the stop signal or a join race during rapid disconnect/reconnect cycles.
Common situations: Handler blocked on a non-cancellation-safe await (e.g. a lock or a send that never completes) while disconnect() aborts it; calling connect() concurrently from multiple tasks.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Coinbase WebSocket handler failed: {error}
- Failed to send SetClient command: {e}
- errors.join("; ") (aggregated shutdown errors)
- non-zero
- subscription state lock poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/7b8c1da04c1e3a03.
Report an issue: GitHub.