{"record":{"id":"61e399328a7741f3","repo":"quickwit-oss/tantivy","slug":"failed-to-acquire-write-lock-on-delete-queue-write","errorCode":null,"errorMessage":"Failed to acquire write lock on delete queue writer","messagePattern":"Failed to acquire write lock on delete queue writer","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/indexer/delete_queue.rs","lineNumber":77,"sourceCode":"\n    /// Creates a new cursor that makes it possible to\n    /// consume future delete operations.\n    ///\n    /// Past delete operations are not accessible.\n    pub fn cursor(&self) -> DeleteCursor {\n        let last_block = self.get_last_block();\n        let operations_len = last_block.operations.len();\n        DeleteCursor {\n            block: last_block,\n            pos: operations_len,\n        }\n    }\n\n    /// Appends a new delete operations.\n    pub fn push(&self, delete_operation: DeleteOperation) {\n        self.inner\n            .write()\n            .expect(\"Failed to acquire write lock on delete queue writer\")\n            .writer\n            .push(delete_operation);\n    }\n\n    // DeleteQueue is a linked list of blocks of\n    // delete operations.\n    //\n    // Writing happens by simply appending to a vec.\n    // `.flush()` takes this pending delete operations vec\n    // creates a new read-only block from it,\n    // and appends it to the linked list.\n    //\n    // `.flush()` happens when, for instance,\n    // a consumer reaches the last read-only operations.\n    // It then ask the delete queue if there happen to\n    // be some unflushed operations.\n    //\n    fn flush(&self) -> Option<Arc<Block>> {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/quickwit-oss/tantivy/blob/b5d8deb80c26924e6b007a5b1a7630f35ca64de4/src/indexer/delete_queue.rs#L59-L95","documentation":"DeleteQueue::push acquires a write lock on the queue's inner state to append a delete operation. The expect() panics if that RwLock was poisoned by a panic in another thread (e.g. a writer thread that crashed while mutating the queue).","triggerScenarios":"Calling index_writer.delete_term(...) or any delete-operation push while another thread panicked while holding the delete queue write lock (most commonly inside flush or a concurrent push).","commonSituations":"Mixed usage where a prior commit/rollback panicked and the user keeps issuing deletes on the same IndexWriter; long-running services swallowing panics in worker threads.","solutions":["Find the original panic that poisoned the delete queue lock in the logs and fix it.","Discard the current IndexWriter and create a new one (the queue state is indeterminate).","Avoid panicking inside threads that touch the IndexWriter; propagate errors instead.","Serialize delete operations behind a higher-level mutex if you mix delete and commit paths in custom code."],"exampleFix":"// before: continuing to use the writer after a panic\nwriter.delete_term(term); // panics: poisoned queue\n// after\nlet mut writer = index.writer(mem); // recreate writer\nwriter.delete_term(term);","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| writer.delete_term(term)));\nif res.is_err() {\n    writer = index.writer(heap_size)?; // recreate: queue is poisoned\n}","preventionTips":["Don't continue using an IndexWriter after any panic in its threads","Validate terms/documents before issuing deletes","Propagate errors instead of panicking in indexing code","Wrap worker threads in catch_unwind and recreate the writer on failure"],"tags":["rust","panic","mutex-poisoning","indexing","deletes"],"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"}