linebender/druid · critical
error allocating shared memory
Error message
error allocating shared memory
What it means
Panic raised by an `.expect()` when creating a `Shm` wrapper over the wayland `wl_shm` global fails during `Surface::new`. Wayland shared-memory (shm) allocation is how the shell gets pixel buffers to the compositor; if the protocol object cannot be created or its initial round-trip fails, there is no fallback path, so the library deliberately aborts surface construction.
Solutions
- Verify the compositor exposes wl_shm (e.g. `wayland-info`); switch to a standard compositor (Mutter, KWin, wlroots-based) if not
- Check WAYLAND_DISPLAY / WAYLAND_SOCKET / XDG_RUNTIME_DIR environment variables point at a live, writable socket
- Ensure the app is not running in a sandbox that blocks wayland globals; adjust sandbox permissions or fall back to X11 backend
- Update wayland-rs / smithay-client-toolkit dependencies and the compositor to compatible versions
Example fix
// before (library code, unavoidable)
let shm = Shm::new(wl_shm).expect("error allocating shared memory");
// after (caller-side guard: don't build surfaces on compositors lacking wl_shm)
let globals = GlobalsState::new_from_thread(&conn).unwrap();
assert!(globals.contents().bind_list().iter().any(|g| g.interface == wl_shm::WlShm::name()), "compositor lacks wl_shm"); Defensive patterns
Strategy: validation
Validate before calling
// Before creating wayland surfaces, confirm wl_shm is bound
// (using wayland-client's globals)
let has_shm = globals
.contents()
.bind_list()
.iter()
.any(|g| g.interface == wl_shm::WlShm::name());
if !has_shm {
eprintln!("compositor does not support wl_shm; cannot render");
std::process::exit(1);
} Try / catch
// Rust panics here, not Results; recover at process level:
let result = std::panic::catch_unwind(|| run_app());
if result.is_err() {
eprintln!("failed to init wayland shm; falling back to X11 backend");
} Prevention
- Verify compositor support with `wayland-info` before deploying to a target environment
- Pin and test wayland-rs/smithay versions against your CI compositor
- Test inside the same sandbox/flatpak permissions used in production
- Keep WAYLAND_DISPLAY/XDG_RUNTIME_DIR checks in your startup diagnostics
When it happens
Trigger: Calling surface/window creation APIs (e.g. `Surface::new` in the wayland backend) on a compositor that does not expose a working `wl_shm` global, a broken/stale wayland connection, or a compositor that rejects the shm protocol object during initialization.
Common situations: Running under a compositor or remote-display setup without wl_shm support (e.g. some nested/special-purpose compositors), connecting over a broken WAYLAND_SOCKET/WAYLAND_DISPLAY environment, sandboxed environments (flatpak/containers) that filter wayland globals, or version mismatches between the client library and compositor.
Related errors
- unexpected wayland event
- unrecognised key event
- attaching an already in-use surface
- unable to acquire underlying compositor to create an xdg…
- unable to acquire underlying compositor to create an xdg…
AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10).
Data as JSON: /api/errors/35244578db9b7d64.
Report an issue: GitHub.
Appendix: source
Thrown at druid-shell/src/backend/wayland/surfaces/buffers.rs:79
pending_buffer_borrowed: Cell<bool>,
/// Shared memory to allocate buffers in
shm: RefCell<Shm>,
}
impl<const N: usize> Buffers<N> {
/// Create a new `Buffers` object.
///
pub fn new(wl_shm: wl::Main<WlShm>, size: RawSize) -> Rc<Self> {
assert!(N >= 2, "must be at least 2 buffers");
Rc::new(Self {
released: Cell::new(Vec::new()),
buffers: Cell::new(None),
pending: Cell::new(0),
size: Cell::new(size),
recreate_buffers: Cell::new(true),
pending_buffer_borrowed: Cell::new(false),
shm: RefCell::new(Shm::new(wl_shm).expect("error allocating shared memory")),
})
}
/// Get the physical size of the buffer.
pub fn size(&self) -> RawSize {
self.size.get()
}
/// Request that the size of the buffer is changed.
pub fn set_size(&self, updated: RawSize) {
assert!(!updated.is_empty(), "window size must not be empty");
let old = self.size.replace(updated);
self.recreate_buffers.set(old != updated);
}
/// Request painting the next frame.
///
/// This calls into user code. To avoid re-entrancy, ensure that we are not already in userView on GitHub (pinned to 0f8b1195e4)