screenpipe/screenpipe · error · anyhow::Error
latest frame mutex poisoned
Error message
latest frame mutex poisoned
What it means
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.
Source
Thrown at crates/screenpipe-screen/src/wgc_capture.rs:597
/// one-time "latest" texture creation. Returns whether a new frame was stored
/// (false for stale-generation frames and pool-recreate transitions).
fn store_frame(
frame: &Direct3D11CaptureFrame,
frame_pool: &Direct3D11CaptureFramePool,
d3d: &Arc<D3dContext>,
latest: &Arc<(Mutex<LatestFrame>, Condvar)>,
closed: &Arc<AtomicBool>,
stats: &Arc<CaptureCounters>,
) -> Result<bool> {
let content = frame
.ContentSize()
.map_err(|e| anyhow!("ContentSize failed: {}", e))?;
let content_size = (content.Width, content.Height);
let mut slot = latest
.0
.lock()
.map_err(|_| anyhow!("latest frame mutex poisoned"))?;
if content_size != slot.pool_size {
// Display mode changed (resolution/scaling): recreate the pool at the new
// size in place. Keeping the session alive avoids re-flashing the capture
// border on Windows 10 and rides out the change without a reinit.
tracing::info!(
"display size changed {:?} -> {:?}, recreating WGC frame pool",
slot.pool_size,
content_size
);
let recreate = create_winrt_device(&d3d.dxgi).and_then(|device| {
frame_pool
.Recreate(
&device,
DirectXPixelFormat::B8G8R8A8UIntNormalized,
FRAME_POOL_BUFFERS,
SizeInt32 {
Width: content.Width,View on GitHub (pinned to 4ebf712990)
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
Example fix
// before
let mut slot = latest.0.lock().map_err(|_| anyhow!("latest frame mutex poisoned"))?;
// after
let mut slot = match latest.0.lock() {
Ok(g) => g,
Err(poisoned) => {
tracing::warn!("latest frame mutex poisoned; recovering slot");
poisoned.into_inner()
}
}; Defensive patterns
Strategy: try-catch
Try / catch
let mut slot = match latest.0.lock() {
Ok(g) => g,
Err(p) => p.into_inner(), // recover the slot; it is a plain value
}; Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Schema and name are required for json_schema response format
- Schema and name are required for json_schema response format
- OCR semaphore is never closed
- background CPU lane is never closed
- Gemini API request failed: ${response.status} ${error}
AI-assisted analysis of screenpipe/screenpipe@4ebf712990 (2026-09-01).
Data as JSON: /api/errors/e64d8496d9638f0e.
Report an issue: GitHub.