linebender/druid · critical
wayland should use unique object IDs
Error message
wayland should use unique object IDs
What it means
This panic fires in the wayland backend's surface handling when a WaylandProxySurface handle with an object ID that is already present in the handles map is inserted. The library treats wayland object IDs as globally unique, so a duplicate ID means an internal invariant violation in handle bookkeeping (handles.borrow_mut().insert(...) returned Some). It is an internal consistency check, not a user-facing validation error.
Solutions
- Update druid/druid-shell to the latest version; duplicate-ID bookkeeping bugs in the wayland backend have been fixed over time.
- Check that you are not cloning or re-registering the same WindowHandle/SurfaceHandle; ensure old handles are dropped before new ones are created for the same surface.
- Test under a mainstream compositor (sway, GNOME, KDE) and current wayland/wayland-client crate versions to rule out compositor-specific ID reuse.
- If reproducible on latest code, file a druid issue with a minimal repro, WAYLAND_DEBUG=1 log, and compositor/version details.
Example fix
// before: registering a second handle with the same id
handles.borrow_mut().insert(handle.id(), handle.clone());
// after: remove any stale entry (or assert) before inserting
let prev = handles.borrow_mut().remove(&handle.id());
debug_assert!(prev.is_none(), "surface id {} reused before cleanup", handle.id());
handles.borrow_mut().insert(handle.id(), handle.clone()); Defensive patterns
Strategy: type-guard
Validate before calling
// before registering a surface handle
let already = appdata.handles.borrow().contains_key(&handle.id());
if already { /* evict or skip instead of panicking */ } Type guard
fn handle_is_fresh(handles: &RefCell<HashMap<u32, Handle>>, h: &Handle) -> bool {
!handles.borrow().contains_key(&h.id())
} Try / catch
// this is a panic, not a Result; recover only at a process/task boundary
let result = std::panic::catch_unwind(|| register_surface(handle.clone()));
if result.is_err() { log::error!("wayland surface registration panicked"); } Prevention
- Always drop/destroy surface handles before their IDs can be reused.
- Avoid keeping stale WindowHandle clones across window recreate cycles.
- Pin druid-shell and wayland-client versions and test on mainstream compositors.
- Run with WAYLAND_DEBUG=1 when debugging surface lifecycle issues.
When it happens
Trigger: Calling the wayland surface registration path (around window.rs:564) twice with the same wayland surface/proxy object ID, e.g. a surface handle being re-registered after being recreated or cloned without the old entry being removed, or a wayland compositor/registrar returning a recycled object ID that was not cleaned up.
Common situations: Running druid apps under wayland with unusual compositors or wayland protocol versions where surface IDs get reused; bugs in the backend's handle-drop/cleanup path leaving stale entries in appdata.handles; embedding or re-creating windows rapidly so a new surface reuses a live ID.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
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/368a724daf36956d.
Report an issue: GitHub.
Appendix: source
Thrown at druid-shell/src/backend/wayland/window.rs:564
let surface =
surfaces::layershell::Surface::new(appdata.clone(), winhandle, self.config.clone());
let handle = WindowHandle::new(
surface.clone(),
surfaces::surface::Dead,
surface.clone(),
surface.clone(),
self.appdata.clone(),
);
if appdata
.handles
.borrow_mut()
.insert(handle.id(), handle.clone())
.is_some()
{
panic!("wayland should use unique object IDs");
}
appdata
.active_surface_id
.borrow_mut()
.push_front(handle.id());
surface.with_handler({
let handle = handle.clone();
move |winhandle| winhandle.connect(&handle.into())
});
Ok(handle)
}
}
}
#[allow(unused)]
pub mod popup {View on GitHub (pinned to 0f8b1195e4)