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

  1. Do not retain WindowHandle beyond window destruction; drop or invalidate stored handles in the window-close handler (window_gone/close callbacks).
  2. Check window liveness before scheduling idle work (query the handle's validity / catch the panic and treat the window as closed).
  3. Route idle work through an application-level scheduler instead of a dead window's idle handle.
  4. 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

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


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)