{"record":{"id":"e64d8496d9638f0e","repo":"screenpipe/screenpipe","slug":"latest-frame-mutex-poisoned","errorCode":null,"errorMessage":"latest frame mutex poisoned","messagePattern":"latest frame mutex poisoned","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/screenpipe-screen/src/wgc_capture.rs","lineNumber":597,"sourceCode":"    /// one-time \"latest\" texture creation. Returns whether a new frame was stored\n    /// (false for stale-generation frames and pool-recreate transitions).\n    fn store_frame(\n        frame: &Direct3D11CaptureFrame,\n        frame_pool: &Direct3D11CaptureFramePool,\n        d3d: &Arc<D3dContext>,\n        latest: &Arc<(Mutex<LatestFrame>, Condvar)>,\n        closed: &Arc<AtomicBool>,\n        stats: &Arc<CaptureCounters>,\n    ) -> Result<bool> {\n        let content = frame\n            .ContentSize()\n            .map_err(|e| anyhow!(\"ContentSize failed: {}\", e))?;\n        let content_size = (content.Width, content.Height);\n\n        let mut slot = latest\n            .0\n            .lock()\n            .map_err(|_| anyhow!(\"latest frame mutex poisoned\"))?;\n\n        if content_size != slot.pool_size {\n            // Display mode changed (resolution/scaling): recreate the pool at the new\n            // size in place. Keeping the session alive avoids re-flashing the capture\n            // border on Windows 10 and rides out the change without a reinit.\n            tracing::info!(\n                \"display size changed {:?} -> {:?}, recreating WGC frame pool\",\n                slot.pool_size,\n                content_size\n            );\n            let recreate = create_winrt_device(&d3d.dxgi).and_then(|device| {\n                frame_pool\n                    .Recreate(\n                        &device,\n                        DirectXPixelFormat::B8G8R8A8UIntNormalized,\n                        FRAME_POOL_BUFFERS,\n                        SizeInt32 {\n                            Width: content.Width,","sourceCodeStart":579,"sourceCodeEnd":615,"githubUrl":"https://github.com/screenpipe/screenpipe/blob/4ebf712990fee17eeaf904dacf749b6e96ac9bf3/crates/screenpipe-screen/src/wgc_capture.rs#L579-L615","documentation":"Raised when the Mutex guarding the LatestFrame slot is poisoned, i.e. another thread panicked while holding the lock on latest.0. std Mutex poisoning means shared frame state may be inconsistent, so store_frame refuses to proceed.","triggerScenarios":"Any thread that previously locked latest.0 panicked while holding the lock (e.g. a bug in the pool-resize path, condvar wait misuse, or an unwrap inside the critical section), leaving the mutex poisoned for all later frame arrivals.","commonSituations":"A panic inside the display-mode-change pool-resize code under the lock; an assertion/unwrap failing in the consumer thread reading LatestFrame; tests panicking on the shared state.","solutions":["Find and fix the original panic in the code that locks latest.0 (check panic backtraces before this error)","Use lock().unwrap_or_else(|p| p.into_inner()) if you decide state is recoverable, since LatestFrame is just a slot","Consider parking_lot::Mutex (no poisoning) if the slot can be safely rebuilt after a panic","Reinitialize the capture pipeline after poisoning instead of failing every subsequent frame"],"exampleFix":"// before\nlet mut slot = latest.0.lock().map_err(|_| anyhow!(\"latest frame mutex poisoned\"))?;\n// after\nlet mut slot = match latest.0.lock() {\n    Ok(g) => g,\n    Err(poisoned) => {\n        tracing::warn!(\"latest frame mutex poisoned; recovering slot\");\n        poisoned.into_inner()\n    }\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let mut slot = match latest.0.lock() {\n    Ok(g) => g,\n    Err(p) => p.into_inner(), // recover the slot; it is a plain value\n};","preventionTips":["Never panic while holding the LatestFrame lock; return Results from the critical section","Audit unwrap()/expect() calls inside lock scopes","Consider parking_lot::Mutex for non-poisoning semantics","Recover via into_inner() since the slot holds no invariants beyond defaults"],"tags":["rust","mutex-poisoned","concurrency"],"backgroundTag":"mutex-poisoned","analyzedSha":"4ebf712990fee17eeaf904dacf749b6e96ac9bf3","analyzedAt":"2026-09-01T23:33:43.065Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}