{"record":{"id":"b135b3ee4944b2bd","repo":"iced-rs/iced","slug":"primitive-storage-should-be-writable","errorCode":null,"errorMessage":"primitive storage should be writable","messagePattern":"primitive storage should be writable","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"wgpu/src/engine.rs","lineNumber":66,"sourceCode":"\n            device,\n            queue,\n            _shell: shell,\n        }\n    }\n\n    #[cfg(any(feature = \"image\", feature = \"svg\"))]\n    pub fn create_image_cache(&self) -> crate::image::Cache {\n        self.image_pipeline\n            .create_cache(&self.device, &self.queue, &self._shell)\n    }\n\n    pub fn trim(&mut self) {\n        self.text_pipeline.trim();\n\n        self.primitive_storage\n            .write()\n            .expect(\"primitive storage should be writable\")\n            .trim();\n    }\n}\n","sourceCodeStart":48,"sourceCodeEnd":70,"githubUrl":"https://github.com/iced-rs/iced/blob/3de451447bd28217bb535632867550908e29d5d0/wgpu/src/engine.rs#L48-L70","documentation":"The wgpu Engine stores primitive::Storage behind an Arc<RwLock> shared by every window compositor. std's RwLock::write returns Err only when the lock is poisoned, i.e. some thread panicked while holding it; this expect in Engine::trim then reports the poisoning, not the original fault. The real panic happened earlier, typically inside a custom primitive's prepare or another window's render pass sharing the same engine.","triggerScenarios":"A multi-window iced_wgpu application where a panic during another window's present() unwinds through the primitive_storage write guard, and a later trim() (widget-cache trimming) hits the poisoned lock; user code that swallowed the first panic with catch_unwind, leaving the lock poisoned.","commonSituations":"Desktop apps with several windows or an embedded viewport plus devtools; custom shader primitive implementations that index or unwrap user data; the first panic being logged above this one in the crash log.","solutions":["Search the log upwards for the FIRST panic: it poisoned the lock and is the actual bug to fix","Make custom Primitive::prepare implementations panic-free (no indexing, unwrap, or division by zero on user data)","Isolate risky prepare code with std::panic::catch_unwind so it cannot poison the shared lock","As a last resort, recover the data with unwrap_or_else(|poisoned| poisoned.into_inner()) in a patched iced"],"exampleFix":"// before (iced internals)\nself.primitive_storage.write().expect(\"primitive storage should be writable\").trim();\n\n// after\nself.primitive_storage\n    .write()\n    .unwrap_or_else(|poisoned| poisoned.into_inner())\n    .trim();","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Recovery pattern when a poisoned shared lock must not kill the process\n// (applies to your own RwLocks, or a patched iced):\nlet mut storage = match engine.primitive_storage.write() {\n    Ok(guard) => guard,\n    Err(poisoned) => {\n        log::error!(\"primitive storage poisoned; recovering\");\n        poisoned.into_inner()\n    }\n};","preventionTips":["The original panic is the bug: always investigate the first crash in the log, not this one","Never panic while holding locks: keep custom Primitive::prepare implementations total","In multi-window apps, isolate untrusted drawing code so one window cannot poison the shared Engine"],"tags":["wgpu","rwlock","poisoned-lock","multi-window","rust"],"backgroundTag":"rwlock-poisoned","analyzedSha":"3de451447bd28217bb535632867550908e29d5d0","analyzedAt":"2026-08-17T10:40:26.997Z","contentChangedAt":"2026-08-17T10:40:26.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}