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
- Fix the underlying panic that poisoned the lock — inspect the panic message that preceded this one; this panic is always secondary.
- Avoid catching panics and reusing the same IndexWriter / IndexWriterStatus across the panic boundary; drop and recreate the writer instead.
- If you intentionally use bombs, ensure the bomb-drop path (kill) cannot panic, and that no user code runs while the lock is held.
- 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
- Never catch_unwind and then keep using the same IndexWriter/IndexWriterStatus
- Do not run arbitrary fallible user code while holding internal writer locks
- Keep bomb-drop paths panic-free
- Recreate the writer after any panic involving it
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
- Failed to acquire read lock on SegmentManager.
- Mmap cache lock is poisoned.
- Lock poisoned. This should never happen
- Field reader cache lock poisoned. This should never happen.
- Failed to acquire write lock on delete queue writer
AI-assisted analysis of quickwit-oss/tantivy@b5d8deb80c (2026-09-05).
Data as JSON: /api/errors/aa96cfb1e9854fae.
Report an issue: GitHub.