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
- Find and fix the original panic that occurred while the subscriptions lock was held (it is the root cause, this panic is secondary)
- Keep subscriber callbacks and Drop implementations panic-free, or catch_unwind around user callbacks
- Use a lock type that tolerates poisoning (parking_lot::Mutex has no poisoning) if recovery is desired
- 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
- Never let user callbacks or Drop impls panic while holding the broker lock
- Use catch_unwind around subscriber trigger calls
- Prefer parking_lot::Mutex (non-poisoning) for broker internals
- One panicking subscriber should not be allowed to poison a shared broker
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
- lock should not be poisoned
- closure always returns Some
- node not found in pending
- OTP logs or traces do not support VRL transforms
- `doc_batch` should not be empty
AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08).
Data as JSON: /api/errors/6fd1f34506170ad3.
Report an issue: GitHub.