GraphiteEditor/Graphite · error
Failed to lock internal overlay context
Error message
Failed to lock internal overlay context
What it means
OverlayContext stores its scene and drawing state behind Arc<Mutex<OverlayContextInternal>>. Clone::clone locks the mutex to snapshot visibility_settings, and .expect("Failed to lock internal overlay context") fires when lock() returns Err. In Rust that happens when the mutex is poisoned (a panic occurred on some thread while holding a guard), after which every later lock panics too — or when the same non-reentrant mutex is locked again on the same thread (reentrant deadlock; on single-threaded WASM this hangs rather than returning Err).
Source
Thrown at editor/src/messages/portfolio/document/overlays/utility_types_native.rs:180
pub fn handles(&self) -> bool {
self.all && self.anchors && self.handles
}
}
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct OverlayContext {
// Serde functionality isn't used but is required by the message system macros
#[serde(skip)]
internal: Arc<Mutex<OverlayContextInternal>>,
pub viewport: ViewportMessageHandler,
pub visibility_settings: OverlaysVisibilitySettings,
}
impl Clone for OverlayContext {
fn clone(&self) -> Self {
let internal = self.internal.lock().expect("Failed to lock internal overlay context");
let visibility_settings = internal.visibility_settings;
drop(internal); // Explicitly release the lock before cloning the Arc<Mutex<_>>
Self {
internal: self.internal.clone(),
viewport: self.viewport,
visibility_settings,
}
}
}
// Manual implementations since Scene doesn't implement PartialEq or Debug
impl PartialEq for OverlayContext {
fn eq(&self, other: &Self) -> bool {
self.viewport == other.viewport && self.visibility_settings == other.visibility_settings
}
}
impl std::fmt::Debug for OverlayContext {View on GitHub (pinned to c507b35645)
Solutions
- Find the FIRST panic in the crash sequence — poisoning is a secondary failure; fix the original panic that held the lock.
- Recover from poisoning instead of panicking: lock().unwrap_or_else(std::sync::PoisonError::into_inner), since the internal scene data is recoverable drawing state.
- Audit for reentrant locking (clone or draw while a guard is alive) and shorten guard lifetimes.
- If contention or reentrancy is structural, replace the Mutex with per-call state or a lock-free design.
Example fix
// before
let internal = self.internal.lock().expect("Failed to lock internal overlay context");
// after
use std::sync::PoisonError;
let internal = self.internal.lock().unwrap_or_else(PoisonError::into_inner); Defensive patterns
Strategy: fallback
Try / catch
use std::sync::PoisonError;
let internal = match self.internal.lock() {
Ok(guard) => guard,
Err(poisoned) => {
log::warn!("overlay context mutex poisoned; recovering");
poisoned.into_inner()
}
}; Prevention
- Treat poisoning as a secondary failure — always find the first panic that held the lock
- Keep guard lifetimes short and panic-free while held
- Avoid cloning OverlayContext from within scopes that already hold the internal guard
When it happens
Trigger: Any panic anywhere between a lock() and guard drop elsewhere in overlay code poisons the mutex; the next OverlayContext::clone() then panics with this message. Alternatively, cloning from within a scope that already holds a guard deadlocks.
Common situations: A sibling overlay panic (drawing expect, take_scene, internal accessor) firing first and poisoning the mutex, turning every subsequent clone into this exact panic; error-path code cloning the context while iterating its internals.
Related errors
- UUID mutex poisoned
- InvalidData
- Failed to parse input type for #[implementation(...)]. Expec
- Expected `->` arrow after input type in #[implementations(..
- Failed to parse output type for #[implementation(...)]. Expe
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/e4254d4d7908ed63.
Report an issue: GitHub.