{"record":{"id":"8d291a489f2da284","repo":"quickwit-oss/tantivy","slug":"the-indexwriter-does-not-have-any-lock-this-is-a","errorCode":null,"errorMessage":"The IndexWriter does not have any lock. This is a bug, please report.","messagePattern":"The IndexWriter does not have any lock\\. This is a bug, please report\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/indexer/index_writer.rs","lineNumber":576,"sourceCode":"    ///\n    /// This cancels all of the updates that\n    /// happened after the last commit.\n    /// After calling rollback, the index is in the same\n    /// state as it was after the last commit.\n    ///\n    /// The opstamp at the last commit is returned.\n    pub fn rollback(&mut self) -> crate::Result<Opstamp> {\n        debug!(\"Rolling back to opstamp {}\", self.committed_opstamp);\n        // marks the segment updater as killed. From now on, all\n        // segment updates will be ignored.\n        self.segment_updater.kill();\n        let document_receiver_res = self.operation_receiver();\n\n        // take the directory lock to create a new index_writer.\n        let directory_lock = self\n            ._directory_lock\n            .take()\n            .expect(\"The IndexWriter does not have any lock. This is a bug, please report.\");\n\n        let new_index_writer =\n            IndexWriter::new(self.index.clone(), self.options.clone(), directory_lock)?;\n\n        // the current `self` is dropped right away because of this call.\n        //\n        // This will drop the document queue, and the thread\n        // should terminate.\n        *self = new_index_writer;\n\n        // Drains the document receiver pipeline :\n        // Workers don't need to index the pending documents.\n        //\n        // This will reach an end as the only document_sender\n        // was dropped with the index_writer.\n        if let Ok(document_receiver) = document_receiver_res {\n            for _ in document_receiver {}\n        }","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/quickwit-oss/tantivy/blob/b5d8deb80c26924e6b007a5b1a7630f35ca64de4/src/indexer/index_writer.rs#L558-L594","documentation":"IndexWriter holds a special directory lock handle that guarantees only one writer exists per index. rollback() takes this lock out of self to construct a replacement writer; if the field is already None the writer invariant is broken, so tantivy panics and asks for a bug report.","triggerScenarios":"Calling rollback() (or commit that internally uses the lock path) more than once after the _directory_lock has already been taken — e.g. calling rollback twice, or using the writer after a rollback replaced it.","commonSituations":"Error-handling code that calls rollback in a cleanup path after an earlier rollback/commit already consumed the lock; using a moved/dropped writer reference; concurrent rollback from two threads.","solutions":["Do not call rollback() twice on the same IndexWriter; after a rollback, only use the newly returned writer.","Restructure cleanup code so rollback runs exactly once (use an Option/flag or a single exit path).","Never share the IndexWriter across threads performing rollback concurrently — rollback requires &mut self / exclusive access.","If hit in normal single-rollback usage, report it as a tantivy bug with a backtrace."],"exampleFix":"// before\nwriter.rollback()?;\n// cleanup path\nwriter.rollback()?; // panics: lock already taken\n// after\nlet mut writer = writer.rollback()?;\n// cleanup only via the new writer; rollback not repeated\nwriter.commit()?;","handlingStrategy":"type-guard","validationCode":"// track rollback usage in your own code\nstruct RollbackGuard { done: bool }\nimpl RollbackGuard {\n    fn can_rollback(&self) -> bool { !self.done }\n}","typeGuard":"fn can_rollback(w: &IndexWriter, already_rolled_back: &mut bool) -> bool {\n    if *already_rolled_back { return false; }\n    *already_rolled_back = true; // mark before calling\n    true\n}","tryCatchPattern":"null","preventionTips":["Call rollback exactly once per IndexWriter; use the writer returned by rollback afterwards","Never run rollback from two threads concurrently","Structure error handling with a single cleanup path (guard/flag)","After rollback, discard the old writer reference completely"],"tags":["rust","panic","index-writer","rollback","lifecycle"],"backgroundTag":"writer-lock-already-taken","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"}