quickwit-oss/tantivy · error

Lock poisoned. This should never happen

Error message

Lock poisoned. This should never happen

What it means

SegmentReader::inverted_index reads from an internal RwLock-cached map of InvertedIndexReaders. The expect() fires when the lock is poisoned because another thread panicked while holding it. Tantivy treats this as unreachable in a healthy process, so it panics rather than returning an error.

Source

Thrown at src/index/segment_reader.rs:228

    }

    /// Returns a field reader associated with the field given in argument.
    /// If the field was not present in the index during indexing time,
    /// the InvertedIndexReader is empty.
    ///
    /// The field reader is in charge of iterating through the
    /// term dictionary associated with a specific field,
    /// and opening the posting list associated with any term.
    ///
    /// If the field is not marked as index, a warning is logged and an empty `InvertedIndexReader`
    /// is returned.
    /// Similarly, if the field is marked as indexed but no term has been indexed for the given
    /// index, an empty `InvertedIndexReader` is returned (but no warning is logged).
    pub fn inverted_index(&self, field: Field) -> crate::Result<Arc<InvertedIndexReader>> {
        if let Some(inv_idx_reader) = self
            .inv_idx_reader_cache
            .read()
            .expect("Lock poisoned. This should never happen")
            .get(&field)
        {
            return Ok(Arc::clone(inv_idx_reader));
        }
        let field_entry = self.schema.get_field_entry(field);
        let field_type = field_entry.field_type();
        let record_option_opt = field_type.get_index_record_option();

        if record_option_opt.is_none() {
            warn!("Field {:?} does not seem indexed.", field_entry.name());
        }

        let postings_file_opt = self.postings_composite.open_read(field);

        if postings_file_opt.is_none() || record_option_opt.is_none() {
            // no documents in the segment contained this field.
            // As a result, no data is associated with the inverted index.
            //

View on GitHub (pinned to b5d8deb80c)

Solutions

  1. Locate the original panic in the logs that poisoned the lock and fix that root cause (usually corrupted segment or IO error).
  2. Recreate the SegmentReader/Searcher after a panic instead of reusing it.
  3. Validate index files (e.g. re-open or re-index the segment) to eliminate the source of the inner panic.
  4. Avoid panicking inside custom query/scorer code that runs while holding segment reader locks.

Example fix

// before: reusing searcher after a worker thread panicked
let inv = segment_reader.inverted_index(field)?; // panics: poisoned
// after: rebuild searcher
let searcher = reader.searcher();
let inv = searcher.segment_reader(seg_ord).inverted_index(field)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// validate segment files exist before searching
for seg in searchable_segments {
    for ext in ["idx", "store", "pos"] {
        if let Some(f) = seg.meta().list_files().iter().find(|f| f.ends_with(ext)) {
            std::fs::metadata(dir.join(f))?;
        }
    }
}

Type guard

fn segment_readable(seg: &Segment) -> bool {
    seg.meta().list_files().iter().all(|f| std::fs::metadata(f).is_ok())
}

Try / catch

let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    searcher.search(&query, &collector)
}));
match res {
    Ok(r) => r,
    Err(_) => { reader.reload()?; searcher = reader.searcher(); retry_search()? }
}

Prevention

When it happens

Trigger: Any call to segment_reader.inverted_index(field) (directly or via scorer/phrase_scorer/fields_metadata/get_match_term_infos) while another thread panicked holding the inv_idx_reader_cache lock.

Common situations: A query execution thread panics (e.g. corrupt or truncated segment files, IO failure during index open) while holding the cache read lock; subsequent searches then panic with this message, masking the root cause.

Related errors


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