linebender/druid · error
get_idle_handle invoked on a dead surface
Error message
get_idle_handle invoked on a dead surface
What it means
druid-shell's dead-surface placeholder implementation panics for operations that make no sense on a dead surface. get_idle_handle is called to obtain an idle-notification handle; on a dead (closed/placeholder) surface there is no such handle, so the library panics unconditionally. Calling window/idle APIs on an already-destroyed surface is a programming error.
Solutions
- Do not retain WindowHandle beyond window destruction; drop or invalidate stored handles in the window-close handler (window_gone/close callbacks).
- Check window liveness before scheduling idle work (query the handle's validity / catch the panic and treat the window as closed).
- Route idle work through an application-level scheduler instead of a dead window's idle handle.
- Use Arc<AtomicBool> or similar flags to cancel pending idle scheduling when the window closes.
Example fix
// before: idle scheduled from a stale handle
fn background_update(win: WindowHandle) {
let idle = win.get_idle_handle(); // panics if window closed
idle.schedule_idle(...);
}
// after: guard with a liveness flag
fn background_update(win: WindowHandle, alive: Arc<AtomicBool>) {
if alive.load(Ordering::SeqCst) {
win.get_idle_handle().schedule_idle(...);
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track window liveness yourself before using idle handles
struct TrackedWindow {
handle: WindowHandle,
alive: Arc<AtomicBool>,
}
fn can_schedule(w: &TrackedWindow) -> bool {
w.alive.load(Ordering::SeqCst)
} Type guard
fn is_alive(w: &TrackedWindow) -> bool {
w.alive.load(Ordering::SeqCst)
} Try / catch
let res = std::panic::catch_unwind(AssertUnwindSafe(|| win.get_idle_handle()));
match res {
Ok(idle) => idle.schedule_idle(...),
Err(_) => { /* window closed; skip idle work */ }
} Prevention
- Invalidate stored WindowHandles in the window-closed callback
- Use an Arc<AtomicBool> liveness flag for all async/window-adjacent work
- Avoid long-lived clones of WindowHandle in background tasks
- Cancel pending idle scheduling during teardown
When it happens
Trigger: Requesting an idle handle (idle::Handle) through a window whose underlying surface has been closed and replaced by the dead-surface placeholder implementation — i.e. any idle scheduling on a destroyed window.
Common situations: Scheduling idle callbacks from a background thread while the window is being closed; holding a WindowHandle past window destruction and then calling get_idle_handle; races between window teardown and async tasks that still reference the window.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- unable to acquire underlying compositor to create an xdg…
- unable to acquire underlying compositor to create an xdg…
- unexpected wayland event
- unrecognised key event
- attaching an already in-use surface
AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10).
Data as JSON: /api/errors/9cffbcd0fd7c1ef0.
Report an issue: GitHub.
Appendix: source
Thrown at druid-shell/src/backend/wayland/surfaces/surface.rs:662
fn request_anim_frame(&self) {
tracing::warn!("request_anim_frame invoked on a dead surface")
}
fn remove_text_field(&self, _token: TextFieldToken) {
tracing::warn!("remove_text_field invoked on a dead surface")
}
fn set_focused_text_field(&self, _active_field: Option<TextFieldToken>) {
tracing::warn!("set_focused_text_field invoked on a dead surface")
}
fn set_input_region(&self, _region: Option<Region>) {
tracing::warn!("set_input_region invoked on a dead surface")
}
fn get_idle_handle(&self) -> idle::Handle {
panic!("get_idle_handle invoked on a dead surface")
}
fn get_scale(&self) -> Scale {
Scale::new(1., 1.)
}
fn invalidate(&self) {
tracing::warn!("invalidate invoked on a dead surface")
}
fn invalidate_rect(&self, _rect: kurbo::Rect) {
tracing::warn!("invalidate_rect invoked on a dead surface")
}
fn run_idle(&self) {
tracing::warn!("run_idle invoked on a dead surface")
}
View on GitHub (pinned to 0f8b1195e4)