quickwit-oss/quickwit · error

lock should not be poisoned

Error message

lock should not be poisoned

What it means

This panic fires when the Mutex guarding the event broker's subscription map is poisoned, i.e. a previous thread panicked while holding the lock. The library treats lock poisoning as an unrecoverable internal invariant violation and aborts with 'lock should not be poisoned' instead of propagating the poison.

Source

Thrown at quickwit/quickwit-common/src/pubsub.rs:81

#[derive(Debug, Default)]
struct InnerEventBroker {
    subscription_sequence: AtomicUsize,
    subscriptions: Mutex<TypeMap>,
}

impl EventBroker {
    // The point of this private method is to allow the public subscribe method to have only one
    // generic argument and avoid the ugly `::<E, _>` syntax.
    fn subscribe_aux<E, S>(&self, subscriber: S, with_timeout: bool) -> EventSubscriptionHandle
    where
        E: Event,
        S: EventSubscriber<E> + Send + Sync + 'static,
    {
        let mut subscriptions = self
            .inner
            .subscriptions
            .lock()
            .expect("lock should not be poisoned");

        if !subscriptions.contains::<EventSubscriptions<E>>() {
            subscriptions.insert::<EventSubscriptions<E>>(HashMap::new());
        }
        let subscription_id = self
            .inner
            .subscription_sequence
            .fetch_add(1, Ordering::Relaxed);

        let subscriber_name = std::any::type_name::<S>();
        let subscription = EventSubscription {
            subscriber_name,
            subscriber: Arc::new(TokioMutex::new(Box::new(subscriber))),
            with_timeout,
        };
        let typed_subscriptions = subscriptions
            .get_mut::<EventSubscriptions<E>>()
            .expect("subscription map should exist");

View on GitHub (pinned to a39730c5cd)

Solutions

  1. Find and fix the original panic that occurred while the subscriptions lock was held (it is the root cause, this panic is secondary)
  2. Keep subscriber callbacks and Drop implementations panic-free, or catch_unwind around user callbacks
  3. Use a lock type that tolerates poisoning (parking_lot::Mutex has no poisoning) if recovery is desired
  4. Recreate the EventBroker instead of reusing a broker whose lock was poisoned

Example fix

// before: user callback can panic inside trigger while lock held
subscription.trigger(event.clone());
// after: isolate user code
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| subscription.trigger(event.clone())));
Defensive patterns

Strategy: try-catch

Validate before calling

// No pre-call validation possible; detect poison via try_lock
if broker.inner.subscriptions.try_lock().is_err() { eprintln!("broker lock contended/poisoned — do not reuse"); }

Type guard

fn broker_healthy<T>(lock: &std::sync::Mutex<T>) -> bool { lock.try_lock().is_ok() }

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| broker.subscribe(subscriber)));
match result { Ok(h) => h, Err(_) => { /* rebuild broker */ } }

Prevention

When it happens

Trigger: Calling EventBroker::subscribe or subscribe_without_timeout after another thread panicked while holding the broker's subscriptions lock (e.g. a subscriber's Drop impl or a publish-triggered callback panicked inside the critical section).

Common situations: A panicking event handler or a buggy EventSubscriber::Drop implementation poisons the lock; subsequent subscribe calls on the shared broker then panic. Often seen in long-lived brokers shared across many test threads.

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


AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08). Data as JSON: /api/errors/6fd1f34506170ad3. Report an issue: GitHub.