iced-rs/iced · error

primitive storage should be writable

Error message

primitive storage should be writable

What it means

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.

Source

Thrown at wgpu/src/engine.rs:66

            device,
            queue,
            _shell: shell,
        }
    }

    #[cfg(any(feature = "image", feature = "svg"))]
    pub fn create_image_cache(&self) -> crate::image::Cache {
        self.image_pipeline
            .create_cache(&self.device, &self.queue, &self._shell)
    }

    pub fn trim(&mut self) {
        self.text_pipeline.trim();

        self.primitive_storage
            .write()
            .expect("primitive storage should be writable")
            .trim();
    }
}

View on GitHub (pinned to 3de451447b)

Solutions

  1. Search the log upwards for the FIRST panic: it poisoned the lock and is the actual bug to fix
  2. Make custom Primitive::prepare implementations panic-free (no indexing, unwrap, or division by zero on user data)
  3. Isolate risky prepare code with std::panic::catch_unwind so it cannot poison the shared lock
  4. As a last resort, recover the data with unwrap_or_else(|poisoned| poisoned.into_inner()) in a patched iced

Example fix

// before (iced internals)
self.primitive_storage.write().expect("primitive storage should be writable").trim();

// after
self.primitive_storage
    .write()
    .unwrap_or_else(|poisoned| poisoned.into_inner())
    .trim();
Defensive patterns

Strategy: fallback

Try / catch

// Recovery pattern when a poisoned shared lock must not kill the process
// (applies to your own RwLocks, or a patched iced):
let mut storage = match engine.primitive_storage.write() {
    Ok(guard) => guard,
    Err(poisoned) => {
        log::error!("primitive storage poisoned; recovering");
        poisoned.into_inner()
    }
};

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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