nautechsystems/nautilus_trader · error

subscription state lock poisoned

Error message

subscription state lock poisoned

What it means

Subscription state (pending/subscribed topics) is guarded by an RwLock; read-side helpers (confirmed, pending_subscribe, len, is_empty, is_subscribed) take a read guard via lock_state_read and panic if the RwLock is poisoned by a panic in a write-side critical section.

Source

Thrown at crates/network/src/websocket/subscription.rs:475

            // Add symbol-level subscriptions (skip marker)
            for symbol in symbols {
                if *symbol != marker {
                    topics.push(format!("{channel}{}{symbol}", self.delimiter));
                }
            }
        }

        // Sort so resubscription after a reconnect replays topics in the same sequence
        // across runs; both the outer DashMap and the inner symbol sets are unordered.
        topics.sort();
        topics
    }

    fn lock_state_read(&self) -> RwLockReadGuard<'_, ()> {
        self.state_lock
            .read()
            .expect("subscription state lock poisoned")
    }

    fn lock_state_write(&self) -> RwLockWriteGuard<'_, ()> {
        self.state_lock
            .write()
            .expect("subscription state lock poisoned")
    }
}

/// Splits a topic into channel and optional symbol using the specified delimiter.
#[must_use]
pub fn split_topic(topic: &str, delimiter: char) -> (&str, Option<&str>) {
    topic
        .split_once(delimiter)
        .map_or((topic, None), |(channel, symbol)| (channel, Some(symbol)))
}

fn snapshot(map: &DashMap<Ustr, AHashSet<Ustr>>) -> SubscriptionSnapshot {

View on GitHub (pinned to d1527c24af)

Solutions

  1. Fix the panic inside the write-side critical sections (keep callbacks/user code outside the guard)
  2. Read guards on a poisoned RwLock can safely recover with into_inner() since reads don't mutate — switch lock_state_read to unwrap_or_else(|e| e.into_inner())
  3. Add tests that panic in a write path and assert reads still work

Example fix

// before
self.state_lock.read().expect("subscription state lock poisoned")
// after
self.state_lock.read().unwrap_or_else(|e| e.into_inner())
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: Any read of subscription state after a thread panicked while holding the write lock — e.g. mark_subscribe/confirm_subscribe mutating topic maps hit an inconsistent state or a downstream callback panicked under the guard.

Common situations: A WS user handler or metrics callback panicking during confirm_subscribe poisons the lock; afterwards every status query (is_subscribed etc.) panics, crashing the actor's message loop.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@d1527c24af (2026-08-27). Data as JSON: /api/errors/c36fa2f0b8eac43d. Report an issue: GitHub.