{"record":{"id":"bf8c0892475e3e05","repo":"GitoxideLabs/gitoxide","slug":"a-write-lock-for-applying-changes","errorCode":null,"errorMessage":"a write lock for applying changes","messagePattern":"a write lock for applying changes","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-ref/src/store/packed/transaction.rs","lineNumber":163,"sourceCode":"            // NOTE that we don't do any additional checks here but apply all edits unconditionally.\n            // This is because this transaction system is internal and will be used correctly from the\n            // loose ref store transactions, which do the necessary checking.\n        }\n        self.edits = Some(edits);\n        Ok(self)\n    }\n\n    /// Commit the prepared transaction.\n    ///\n    /// Please note that actual edits invalidated existing packed buffers.\n    /// Note: There is the potential to write changes into memory and return such a packed-refs buffer for reuse.\n    pub fn commit(self) -> Result<(), commit::Error> {\n        let mut edits = self.edits.expect(\"BUG: cannot call commit() before prepare(…)\");\n        if edits.is_empty() {\n            return Ok(());\n        }\n\n        let mut file = self.lock.expect(\"a write lock for applying changes\");\n        let refs_sorted: Box<dyn Iterator<Item = Result<packed::Reference<'_>, packed::iter::Error>>> =\n            match self.buffer.as_ref() {\n                Some(buffer) => Box::new(buffer.iter()?),\n                None => Box::new(std::iter::empty()),\n            };\n\n        let mut refs_sorted = refs_sorted.peekable();\n\n        edits.sort_by(|l, r| l.inner.name.as_bstr().cmp(r.inner.name.as_bstr()));\n        let mut peekable_sorted_edits = edits.iter().peekable();\n\n        file.with_mut(|f| f.write_all(HEADER_LINE))?;\n\n        let mut num_written_lines = 0;\n        loop {\n            match (refs_sorted.peek(), peekable_sorted_edits.peek()) {\n                (Some(Err(_)), _) => {\n                    let err = refs_sorted.next().expect(\"next\").expect_err(\"err\");","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-ref/src/store/packed/transaction.rs#L145-L181","documentation":"Panic raised by `self.lock.expect(\"a write lock for applying changes\")` inside `packed::Transaction::commit()`. The write lock is acquired during `prepare`; its absence at commit time means commit was called without a successful prepare, or the lock was already consumed. It is an internal consistency check on the two-phase transaction lifecycle.","triggerScenarios":"Calling `packed::Transaction::commit()` when `prepare()` was never called, failed to acquire the lock, or the transaction's lock was taken elsewhere — i.e. the transaction is not in the prepared state.","commonSituations":"Ignoring the `Result` of `prepare()` and proceeding to commit; concurrent code paths where another component consumed or released the packed-refs lock; refactored two-phase flows.","solutions":["Check the `Result` returned by `prepare()` and abort on error instead of calling `commit()`.","Always `prepare()` (which acquires the lock) before `commit()`.","Ensure only one code path owns the transaction and its lock."],"exampleFix":"// before\nlet mut tx = store.packed_transaction(lock, opts)?;\nlet _ = tx.prepare(edits, &objects, true); // error ignored\ntx.commit()?;\n// after\nlet mut tx = store.packed_transaction(lock, opts)?;\ntx.prepare(edits, &objects, true)?; // aborts on failure\ntx.commit()?;","handlingStrategy":"type-guard","validationCode":"// prepare() acquires the lock; verify it succeeded before commit\ntx.prepare(edits, &objects, true).map_err(|e| format!(\"prepare failed: {e}\"))?;\ntx.commit()?;","typeGuard":"fn ensure_prepared_and_commit(tx: gix_ref::transaction::PackedTransaction, r: Result<(), PrepareErr>) -> Result<(), Box<dyn std::error::Error>> {\n    r?; // propagate prepare failure; do not fall through to commit\n    tx.commit().map_err(Into::into)\n}","tryCatchPattern":"// treat prepare error as terminal\nmatch tx.prepare(edits, &objects, true) {\n    Ok(()) => tx.commit()?,\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Always check prepare()'s Result before calling commit().","Ensure the packed-refs lock isn't released or consumed elsewhere.","Avoid sharing a transaction across threads or retry loops that re-enter commit."],"tags":["panic","lock","transaction","packed-refs"],"backgroundTag":"invalid-state-transition","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}