linebender/druid · error

should never recompute scale of window that has been dropped

Error message

should never recompute scale of window that has been dropped

What it means

Generic guard in CompositorHandle::recompute_scale: the Weak<dyn Compositor> held by the handle has been dropped, so the scale cannot be recomputed. It fires when output configuration changes (outputs added/removed) arrive for a window whose compositor backend has already been dropped, i.e. a use-after-drop of the window's wayland resources.

Solutions

  1. Ensure the CompositorHandle is dropped together with (or before) the compositor it points to, so no events are delivered to a dead handle
  2. Check Weak::upgrade success before handling output/scale events and skip processing when the compositor is gone
  3. Keep a strong reference alive as long as the surface can receive wayland events
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at druid-shell/src/backend/wayland/surfaces/mod.rs:100 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10). Data as JSON: /api/errors/2b76dc5182e8f486. Report an issue: GitHub.

Appendix: source

Thrown at druid-shell/src/backend/wayland/surfaces/mod.rs:100

impl CompositorHandle {
    pub fn new(c: impl Into<CompositorHandle>) -> Self {
        c.into()
    }

    pub fn direct(c: std::sync::Weak<dyn Compositor>) -> Self {
        Self { inner: c }
    }

    fn create_surface(&self) -> Option<wlc::Main<WlSurface>> {
        self.inner.upgrade().map(|c| c.create_surface())
    }

    /// Recompute the scale to use (the maximum of all the provided outputs).
    fn recompute_scale(&self, outputs: &std::collections::HashSet<u32>) -> i32 {
        let compositor = match self.inner.upgrade() {
            Some(c) => c,
            None => panic!("should never recompute scale of window that has been dropped"),
        };
        tracing::debug!("computing scale using {:?} outputs", outputs.len());
        let scale = outputs.iter().fold(0, |scale, id| {
            tracing::debug!("recomputing scale using output {:?}", id);
            match compositor.output(*id) {
                None => {
                    tracing::warn!(
                        "we still have a reference to an output that's gone away. The output had id {}",
                        id,
                    );
                    scale
                },
                Some(output) => scale.max(output.scale as i32),
            }
        });

        match scale {
            0 => {

View on GitHub (pinned to 0f8b1195e4)