{"record":{"id":"14c7f462504efc55","repo":"quickwit-oss/tantivy","slug":"failed-to-acquire-write-lock-in-delete-queue","errorCode":null,"errorMessage":"Failed to acquire write lock in delete queue","messagePattern":"Failed to acquire write lock in delete queue","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/indexer/delete_queue.rs","lineNumber":136,"sourceCode":"    Writer(DeleteQueue),\n    Closed(Arc<Block>),\n}\n\nstruct NextBlock(RwLock<InnerNextBlock>);\n\nimpl From<DeleteQueue> for NextBlock {\n    fn from(delete_queue: DeleteQueue) -> NextBlock {\n        NextBlock(RwLock::new(InnerNextBlock::Writer(delete_queue)))\n    }\n}\n\nimpl NextBlock {\n    fn next_block(&self) -> Option<Arc<Block>> {\n        {\n            let next_read_lock = self\n                .0\n                .read()\n                .expect(\"Failed to acquire write lock in delete queue\");\n            if let InnerNextBlock::Closed(ref block) = *next_read_lock {\n                return Some(Arc::clone(block));\n            }\n        }\n        let next_block;\n        {\n            let mut next_write_lock = self\n                .0\n                .write()\n                .expect(\"Failed to acquire write lock in delete queue\");\n            match *next_write_lock {\n                InnerNextBlock::Closed(ref block) => {\n                    return Some(Arc::clone(block));\n                }\n                InnerNextBlock::Writer(ref writer) => match writer.flush() {\n                    Some(flushed_next_block) => {\n                        next_block = flushed_next_block;\n                    }","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/quickwit-oss/tantivy/blob/b5d8deb80c26924e6b007a5b1a7630f35ca64de4/src/indexer/delete_queue.rs#L118-L154","documentation":"NextBlock::next_block takes a read lock to check whether the garbage-collector side of the delete queue has reached a closed block. The expect message mentions 'write lock' but the actual failure is a poisoned RwLock (read side): a previous holder panicked, so the queue cursor state is unreliable.","triggerScenarios":"The delete-GC worker calls next_block() while another thread has panicked holding the queue lock (e.g. in flush() or push()).","commonSituations":"Background garbage collection of delete blocks running alongside a panicking indexing thread; usually a secondary symptom of an earlier crash.","solutions":["Fix the original panic that poisoned the delete queue lock.","Restart indexing / recreate the IndexWriter and its GC worker.","Harden flush/push paths against panics (no unwraps on user data).","Isolate GC in its own thread with catch_unwind so poisoning does not propagate."],"exampleFix":"// before\nblock_garbage_collector.next_block(); // panics after unrelated panic\n// after\nlet next = std::panic::catch_unwind(|| gc.next_block());\nif next.is_err() { restart_gc_worker(&queue); }","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"let next = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| gc.next_block()));\nif next.is_err() { restart_delete_gc(&index)?; }","preventionTips":["Isolate the GC thread with panic recovery + restart","Fix root panics in push/flush paths first","Avoid unwraps on user-supplied data in delete operations","Monitor for the first panic — this one is secondary"],"tags":["rust","panic","mutex-poisoning","concurrency","gc"],"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"}