{"record":{"id":"9dadce21539e38be","repo":"iced-rs/iced","slug":"write-primitive-storage","errorCode":null,"errorMessage":"Write primitive storage","messagePattern":"Write primitive storage","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"wgpu/src/lib.rs","lineNumber":343,"sourceCode":"                    &self.engine.device,\n                    &mut self.staging_belt,\n                    encoder,\n                    &layer.triangles,\n                    Transformation::scale(scale_factor),\n                    viewport.physical_size(),\n                );\n\n                prepare_span.finish();\n            }\n\n            if !layer.primitives.is_empty() {\n                let prepare_span = debug::prepare(debug::Primitive::Shader);\n\n                let mut primitive_storage = self\n                    .engine\n                    .primitive_storage\n                    .write()\n                    .expect(\"Write primitive storage\");\n\n                for instance in &layer.primitives {\n                    instance.primitive.prepare(\n                        &mut primitive_storage,\n                        &self.engine.device,\n                        &self.engine.queue,\n                        self.engine.format,\n                        &instance.bounds,\n                        viewport,\n                    );\n                }\n\n                prepare_span.finish();\n            }\n\n            #[cfg(any(feature = \"svg\", feature = \"image\"))]\n            if !layer.images.is_empty() {\n                let prepare_span = debug::prepare(debug::Primitive::Image);","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/iced-rs/iced/blob/a8ff2d522552c1252b88afc02bc310dd657cd25e/wgpu/src/lib.rs#L325-L361","documentation":"During Compositor::present, iced takes the write lock on the shared Arc<RwLock<primitive::Storage>> to call prepare on each primitive instance (uploading meshes, gradients, clip masks, custom shader data). The expect fires only when the lock is poisoned, meaning some earlier panic unwound while holding it; the poisoning site, not this line, is the bug.","triggerScenarios":"A panic inside any Primitive::prepare implementation (custom shader primitives, cached gradients, image uploads) unwinds through the write guard during a previous frame; in a multi-window app the poisoned lock then makes every other window's next frame abort here.","commonSituations":"Custom primitive implementations that slice or unwrap application data; panics in image decoding inside prepare; one window crashing and taking down all others that share the cloned Engine.","solutions":["Find and fix the original panic earlier in the log; this line is only the symptom","Make custom prepare() implementations total: no panicking indexing, unwrap, or arithmetic overflow","Wrap risky logic inside prepare in std::panic::catch_unwind(AssertUnwindSafe(..)) so a bug cannot poison the shared lock"],"exampleFix":"// before\nimpl Primitive for MyShader {\n    fn prepare(&mut self, ...) {\n        let item = self.items[i]; // panics -> poisons shared storage lock\n    }\n}\n\n// after\nimpl Primitive for MyShader {\n    fn prepare(&mut self, ...) {\n        let item = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.items[i]))\n            .unwrap_or_else(|_| panic::resume_unwind); // or a safe default\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Wrap risky work inside custom Primitive::prepare so a bug cannot poison\n// the shared primitive storage lock for every window:\nlet prepared = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    // indexing / unwraps on application data\n    self.items[self.index].clone()\n}));\nmatch prepared {\n    Ok(item) => { /* normal prepare path */ }\n    Err(payload) => {\n        log::error!(\"primitive prepare failed: {payload:?}\");\n        // fall back to a safe default instead of unwinding through the lock\n    }\n}","preventionTips":["Treat prepare() as a sandbox: no panicking indexing, unwrap, or slice math on user data","Log and continue on bad primitive data rather than unwinding inside the render pass","Test custom primitives with empty/edge-case payloads before shipping"],"tags":["wgpu","rwlock","poisoned-lock","custom-primitive","rust"],"backgroundTag":"rwlock-poisoned","analyzedSha":"a8ff2d522552c1252b88afc02bc310dd657cd25e","analyzedAt":"2026-08-17T10:40:26.997Z","contentChangedAt":"2026-08-17T10:40:26.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}