quickwit-oss/tantivy · error

This lock should never be poisoned

Error message

This lock should never be poisoned

What it means

This panic comes from unwrapping a std::sync::RwLock read guard in IndexWriterStatus::operation_receiver (src/indexer/index_writer_status.rs:26). It fires only if the mutex is poisoned, i.e. a thread panicked while holding the lock. The code asserts this can never happen because the guard body never panics: it only clones an Option<AddBatchReceiver>.

Source

Thrown at src/indexer/index_writer_status.rs:26

#[derive(Clone)]
pub(crate) struct IndexWriterStatus<D: Document = TantivyDocument> {
    inner: Arc<Inner<D>>,
}

impl<D: Document> IndexWriterStatus<D> {
    /// Returns true iff the index writer is alive.
    pub fn is_alive(&self) -> bool {
        self.inner.as_ref().is_alive()
    }

    /// Returns a copy of the operation receiver.
    /// If the index writer was killed, returns `None`.
    pub fn operation_receiver(&self) -> Option<AddBatchReceiver<D>> {
        let rlock = self
            .inner
            .receive_channel
            .read()
            .expect("This lock should never be poisoned");
        rlock.as_ref().cloned()
    }

    /// Create an index writer bomb.
    /// If dropped, the index writer status will be killed.
    pub(crate) fn create_bomb(&self) -> IndexWriterBomb<D> {
        IndexWriterBomb {
            inner: Some(self.inner.clone()),
        }
    }
}

struct Inner<D: Document> {
    is_alive: AtomicBool,
    receive_channel: RwLock<Option<AddBatchReceiver<D>>>,
}

impl<D: Document> Inner<D> {

View on GitHub (pinned to b5d8deb80c)

Solutions

  1. Fix the underlying panic that poisoned the lock — inspect the panic message that preceded this one; this panic is always secondary.
  2. Avoid catching panics and reusing the same IndexWriter / IndexWriterStatus across the panic boundary; drop and recreate the writer instead.
  3. If you intentionally use bombs, ensure the bomb-drop path (kill) cannot panic, and that no user code runs while the lock is held.
  4. Update tantivy — if you can reproduce poisoning without an obvious source panic, report it; the invariant may be violated by a library bug.

Example fix

// before: reuse after caught panic
let res = std::panic::catch_unwind(|| writer_operation());
// ... continue using same writer.status.operation_receiver()

// after: rebuild the writer if a panic occurred
let res = std::panic::catch_unwind(|| writer_operation());
if res.is_err() { writer = recreate_writer(); }
Defensive patterns

Strategy: try-catch

Validate before calling

// best effort: only access the writer from one logical owner
// let recv = writer.status.operation_receiver(); // only while no bomb may drop concurrently

Try / catch

// Rust panics cannot be caught with try/catch; isolate the whole writer scope
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
    use_index_writer(&writer)
}));
if result.is_err() { writer = reopen_writer(); }

Prevention

When it happens

Trigger: Calling IndexWriterStatus::operation_receiver() after another thread panicked while holding the receive_channel RwLock write guard (e.g. inside kill(), which is invoked by a dropped IndexWriterBomb). In practice this requires a genuine bug elsewhere that panics between lock acquisition and release.

Common situations: A user creates an IndexWriterBomb and drops it in a thread that also panics nearby; mixing panics with multi-threaded index writer access; catching panics with catch_unwind and continuing to use the writer afterwards, leaving a poisoned lock behind.

Related errors


AI-assisted analysis of quickwit-oss/tantivy@b5d8deb80c (2026-09-05). Data as JSON: /api/errors/aa96cfb1e9854fae. Report an issue: GitHub.