{"record":{"id":"6535de426e8d1c26","repo":"quickwit-oss/tantivy","slug":"lock-poisoned-this-should-never-happen","errorCode":null,"errorMessage":"Lock poisoned. This should never happen","messagePattern":"Lock poisoned\\. This should never happen","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/index/segment_reader.rs","lineNumber":228,"sourceCode":"    }\n\n    /// Returns a field reader associated with the field given in argument.\n    /// If the field was not present in the index during indexing time,\n    /// the InvertedIndexReader is empty.\n    ///\n    /// The field reader is in charge of iterating through the\n    /// term dictionary associated with a specific field,\n    /// and opening the posting list associated with any term.\n    ///\n    /// If the field is not marked as index, a warning is logged and an empty `InvertedIndexReader`\n    /// is returned.\n    /// Similarly, if the field is marked as indexed but no term has been indexed for the given\n    /// index, an empty `InvertedIndexReader` is returned (but no warning is logged).\n    pub fn inverted_index(&self, field: Field) -> crate::Result<Arc<InvertedIndexReader>> {\n        if let Some(inv_idx_reader) = self\n            .inv_idx_reader_cache\n            .read()\n            .expect(\"Lock poisoned. This should never happen\")\n            .get(&field)\n        {\n            return Ok(Arc::clone(inv_idx_reader));\n        }\n        let field_entry = self.schema.get_field_entry(field);\n        let field_type = field_entry.field_type();\n        let record_option_opt = field_type.get_index_record_option();\n\n        if record_option_opt.is_none() {\n            warn!(\"Field {:?} does not seem indexed.\", field_entry.name());\n        }\n\n        let postings_file_opt = self.postings_composite.open_read(field);\n\n        if postings_file_opt.is_none() || record_option_opt.is_none() {\n            // no documents in the segment contained this field.\n            // As a result, no data is associated with the inverted index.\n            //","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/quickwit-oss/tantivy/blob/b5d8deb80c26924e6b007a5b1a7630f35ca64de4/src/index/segment_reader.rs#L210-L246","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Locate the original panic in the logs that poisoned the lock and fix that root cause (usually corrupted segment or IO error).","Recreate the SegmentReader/Searcher after a panic instead of reusing it.","Validate index files (e.g. re-open or re-index the segment) to eliminate the source of the inner panic.","Avoid panicking inside custom query/scorer code that runs while holding segment reader locks."],"exampleFix":"// before: reusing searcher after a worker thread panicked\nlet inv = segment_reader.inverted_index(field)?; // panics: poisoned\n// after: rebuild searcher\nlet searcher = reader.searcher();\nlet inv = searcher.segment_reader(seg_ord).inverted_index(field)?;","handlingStrategy":"try-catch","validationCode":"// validate segment files exist before searching\nfor seg in searchable_segments {\n    for ext in [\"idx\", \"store\", \"pos\"] {\n        if let Some(f) = seg.meta().list_files().iter().find(|f| f.ends_with(ext)) {\n            std::fs::metadata(dir.join(f))?;\n        }\n    }\n}","typeGuard":"fn segment_readable(seg: &Segment) -> bool {\n    seg.meta().list_files().iter().all(|f| std::fs::metadata(f).is_ok())\n}","tryCatchPattern":"let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    searcher.search(&query, &collector)\n}));\nmatch res {\n    Ok(r) => r,\n    Err(_) => { reader.reload()?; searcher = reader.searcher(); retry_search()? }\n}","preventionTips":["Validate index integrity before concurrent searching","Never panic inside custom scorers or query impls","Reload/reopen the reader after a panic instead of reusing it","Catch panics at worker-thread boundaries"],"tags":["rust","panic","mutex-poisoning","concurrency","search"],"backgroundTag":"mutex-poisoned","analyzedSha":"b5d8deb80c26924e6b007a5b1a7630f35ca64de4","analyzedAt":"2026-09-05T13:20:51.521Z","contentChangedAt":"2026-09-05T13:20:51.521Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}