nautechsystems/nautilus_trader · error
{e}; subscription rollback failed: {close_error}
Error message
{e}; subscription rollback failed: {close_error} What it means
When subscribe_one re-checks pool openness after a shard was assigned, finding the pool closed triggers a rollback: the token assignment is released and, if the shard became empty, it is closed. If that rollback close itself fails, the original 'pool is closed' error and the rollback close error are chained into this combined message; otherwise the original error is returned alone.
Source
Thrown at crates/adapters/polymarket/src/websocket/pool.rs:529
out_rx: Mutex::new(None),
socket_factory: Mutex::new(None),
closed: AtomicBool::new(false),
}
}
// Callers hold `wire_mutex`.
async fn subscribe_one(&self, asset_id: String) -> anyhow::Result<()> {
let token = Ustr::from(asset_id.as_str());
let Some(handle) = self.assign(token).await? else {
return Ok(());
};
if let Err(e) = self.ensure_open() {
if let ReleaseOutcome::CloseShard(id, shard) = self.release(token)
&& let Err(close_error) = self.close_shard(id, shard).await
{
anyhow::bail!("{e}; subscription rollback failed: {close_error}");
}
return Err(e);
}
if let Err(e) = handle.subscribe_market(vec![asset_id]).await {
// Roll back so a failed send leaves no stale assignment or empty shard.
if let ReleaseOutcome::CloseShard(id, shard) = self.release(token)
&& let Err(close_error) = self.close_shard(id, shard).await
{
anyhow::bail!("{e}; subscription rollback failed: {close_error}");
}
return Err(e);
}
Ok(())
}
// Callers hold `wire_mutex`.
async fn unsubscribe_one(&self, asset_id: String) -> anyhow::Result<()> {View on GitHub (pinned to 18893faf8b)
Solutions
- Treat both errors as terminal: the pool is closed — reconnect with a new pool instead of retrying
- Avoid racing subscribe against disconnect; cancel subscriptions before initiating shutdown
- Log both the original and rollback errors from the combined message for diagnosis
- If the rollback failure indicates an already-dead connection, cleanup should tolerate it — retry disconnect/drop the pool
Defensive patterns
Strategy: retry
Validate before calling
if pool.is_closed() { pool = reconnect_pool()?; } Try / catch
match pool.subscribe_one(asset_id).await {
Err(e) if e.to_string().contains("pool is closed") => {
let pool = reconnect_pool()?;
pool.subscribe_one(asset_id).await?;
}
other => other?,
} Prevention
- Cancel in-flight subscriptions before shutdown
- Use a lifecycle owner to order connect/subscribe/disconnect
- Treat chained rollback errors as terminal closed-pool conditions
- Avoid sharing pool handles across tasks that also disconnect
When it happens
Trigger: A disconnect/close races with subscribe_one: the pool closes between the initial ensure_open and the post-assignment ensure_open, AND close_shard fails during rollback (e.g., the shard's connection is already dead).
Common situations: Adapter shutdown while subscription requests are in flight; simultaneous disconnect and subscribe from different tasks.
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
- RTDS connection was canceled by shutdown
- Market connection pool is closed; shard rollback failed: {e}
- Failed to start market shard forwarder: {e}; startup rollbac
- Polymarket WebSocket startup rollback failed: {shutdown_erro
- Lighter WebSocket initial connection cancelled
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/57a881746369ce84.
Report an issue: GitHub.