{"record":{"id":"e4254d4d7908ed63","repo":"GraphiteEditor/Graphite","slug":"failed-to-lock-internal-overlay-context","errorCode":null,"errorMessage":"Failed to lock internal overlay context","messagePattern":"Failed to lock internal overlay context","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"editor/src/messages/portfolio/document/overlays/utility_types_native.rs","lineNumber":180,"sourceCode":"\n\tpub fn handles(&self) -> bool {\n\t\tself.all && self.anchors && self.handles\n\t}\n}\n\n#[cfg_attr(feature = \"wasm\", derive(tsify::Tsify))]\n#[derive(serde::Serialize, serde::Deserialize)]\npub struct OverlayContext {\n\t// Serde functionality isn't used but is required by the message system macros\n\t#[serde(skip)]\n\tinternal: Arc<Mutex<OverlayContextInternal>>,\n\tpub viewport: ViewportMessageHandler,\n\tpub visibility_settings: OverlaysVisibilitySettings,\n}\n\nimpl Clone for OverlayContext {\n\tfn clone(&self) -> Self {\n\t\tlet internal = self.internal.lock().expect(\"Failed to lock internal overlay context\");\n\t\tlet visibility_settings = internal.visibility_settings;\n\t\tdrop(internal); // Explicitly release the lock before cloning the Arc<Mutex<_>>\n\t\tSelf {\n\t\t\tinternal: self.internal.clone(),\n\t\t\tviewport: self.viewport,\n\t\t\tvisibility_settings,\n\t\t}\n\t}\n}\n\n// Manual implementations since Scene doesn't implement PartialEq or Debug\nimpl PartialEq for OverlayContext {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.viewport == other.viewport && self.visibility_settings == other.visibility_settings\n\t}\n}\n\nimpl std::fmt::Debug for OverlayContext {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/portfolio/document/overlays/utility_types_native.rs#L162-L198","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet internal = self.internal.lock().expect(\"Failed to lock internal overlay context\");\n\n// after\nuse std::sync::PoisonError;\nlet internal = self.internal.lock().unwrap_or_else(PoisonError::into_inner);","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"use std::sync::PoisonError;\nlet internal = match self.internal.lock() {\n\tOk(guard) => guard,\n\tErr(poisoned) => {\n\t\tlog::warn!(\"overlay context mutex poisoned; recovering\");\n\t\tpoisoned.into_inner()\n\t}\n};","preventionTips":["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"],"tags":["rust","mutex","poisoning","overlay","concurrency"],"backgroundTag":"mutex-poisoned","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}