iced-rs/iced · error
Write primitive storage
Error message
Write primitive storage
What it means
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.
Source
Thrown at wgpu/src/lib.rs:343
&self.engine.device,
&mut self.staging_belt,
encoder,
&layer.triangles,
Transformation::scale(scale_factor),
viewport.physical_size(),
);
prepare_span.finish();
}
if !layer.primitives.is_empty() {
let prepare_span = debug::prepare(debug::Primitive::Shader);
let mut primitive_storage = self
.engine
.primitive_storage
.write()
.expect("Write primitive storage");
for instance in &layer.primitives {
instance.primitive.prepare(
&mut primitive_storage,
&self.engine.device,
&self.engine.queue,
self.engine.format,
&instance.bounds,
viewport,
);
}
prepare_span.finish();
}
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
let prepare_span = debug::prepare(debug::Primitive::Image);View on GitHub (pinned to a8ff2d5225)
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
Example fix
// before
impl Primitive for MyShader {
fn prepare(&mut self, ...) {
let item = self.items[i]; // panics -> poisons shared storage lock
}
}
// after
impl Primitive for MyShader {
fn prepare(&mut self, ...) {
let item = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.items[i]))
.unwrap_or_else(|_| panic::resume_unwind); // or a safe default
}
} Defensive patterns
Strategy: try-catch
Try / catch
// Wrap risky work inside custom Primitive::prepare so a bug cannot poison
// the shared primitive storage lock for every window:
let prepared = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
// indexing / unwraps on application data
self.items[self.index].clone()
}));
match prepared {
Ok(item) => { /* normal prepare path */ }
Err(payload) => {
log::error!("primitive prepare failed: {payload:?}");
// fall back to a safe default instead of unwinding through the lock
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- primitive storage should be writable
- Write text atlas
- Write to text atlas
- Read text atlas
- Read primitive storage
AI-assisted analysis of iced-rs/iced@a8ff2d5225 (2026-08-17).
Data as JSON: /api/errors/9dadce21539e38be.
Report an issue: GitHub.