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

  1. Find and fix the original panic earlier in the log; this line is only the symptom
  2. Make custom prepare() implementations total: no panicking indexing, unwrap, or arithmetic overflow
  3. 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

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


AI-assisted analysis of iced-rs/iced@a8ff2d5225 (2026-08-17). Data as JSON: /api/errors/9dadce21539e38be. Report an issue: GitHub.