{"record":{"id":"8ab67658d424289c","repo":"block/buzz","slug":"fence-lock-poisoned","errorCode":null,"errorMessage":"fence lock poisoned","messagePattern":"fence lock poisoned","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-db/src/runtime/replica_fence.rs","lineNumber":199,"sourceCode":"    }\n}\n\n/// Shared fence state. `Db` holds an `Arc` of this; the probe task records\n/// entries and per-request routing resolves proofs against it.\n#[derive(Debug, Default)]\npub struct ReplicaFence {\n    inner: Mutex<FenceInner>,\n}\n\nimpl ReplicaFence {\n    /// A new fence, initially closed (empty ring).\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Close the fence: drop all retained proofs; reads route to the writer.\n    pub fn close(&self) {\n        let mut inner = self.inner.lock().expect(\"fence lock poisoned\");\n        inner.ring.clear();\n    }\n\n    /// Record one probe sample. Epoch changes (re-seed) clear the ring and\n    /// start a new one under the observed epoch — sound, because an entry\n    /// only proves commits on its own timeline and readers must match the\n    /// epoch to cite it. A same-epoch token regression is the unsafe case:\n    /// see [`RecordOutcome::TokenRegression`].\n    pub fn record(\n        &self,\n        token: i64,\n        epoch: Uuid,\n        committed_at: Instant,\n        fence_wall: DateTime<Utc>,\n    ) -> RecordOutcome {\n        let mut inner = self.inner.lock().expect(\"fence lock poisoned\");\n        if inner.epoch != Some(epoch) {\n            inner.ring.clear();","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-db/src/runtime/replica_fence.rs#L181-L217","documentation":"ReplicaFence::close clears the retained proof ring under a std Mutex. The panic message \"fence lock poisoned\" comes from lock().expect(...) — it fires when the mutex is poisoned, i.e. another thread panicked while holding the fence lock, leaving it permanently poisoned for all subsequent lock() calls. The close operation itself is trivial; the real failure is the earlier panic.","triggerScenarios":"Calling close(), record(), or any lock-taking method after some other thread panicked while mutating the fence's inner state (e.g. during a probe sample recording or ring mutation).","commonSituations":"A panic inside a probe worker (run_probe) or scratch-db teardown (drop_scratch_db) poisons the lock; subsequent fence tests like fence_starts_closed_and_opens_on_record then fail with this message, masking the original panic.","solutions":["Find and fix the original panic that poisoned the mutex — inspect the first panic backtrace in the test log, not this expect message.","If the fence must survive internal panics, switch to parking_lot::Mutex (no poisoning) or handle lock().unwrap_or_else(|e| e.into_inner()) for lock-free recovery.","Ensure ring-mutation code paths cannot panic (avoid indexing/unwrap inside the locked region).","Re-run the failing test in isolation with --nocapture to capture the root panic."],"exampleFix":"// before\nlet mut inner = self.inner.lock().expect(\"fence lock poisoned\");\n// after\nlet mut inner = self.inner.lock().unwrap_or_else(|poisoned| poisoned.into_inner());","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let mut inner = match self.inner.lock() {\n    Ok(g) => g,\n    Err(poisoned) => {\n        tracing::error!(\"fence lock poisoned by earlier panic\");\n        poisoned.into_inner() // recover fence state; fix root panic separately\n    }\n};","preventionTips":["Never panic while holding a std Mutex — return Results inside the locked region.","Prefer parking_lot::Mutex for locks whose invariants survive internal errors.","When this message appears, hunt the ORIGINAL panic in the log; it is a symptom, not the cause."],"tags":["rust","concurrency","mutex","poisoned-lock"],"backgroundTag":"mutex-poisoned","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}