linebender/druid · error

attaching an already in-use surface

Error message

attaching an already in-use surface

What it means

`Buffer::attach` panics if the buffer is already flagged `in_use`, meaning it is still attached to a surface and has not been released by the compositor. Attaching it again would give two surfaces the same buffer, which the Wayland protocol forbids.

Solutions

  1. Increase the buffer pool size so a free buffer is always available
  2. Wait for buffer release (frame callbacks / `wl_buffer.release`) before reattaching
  3. Ensure buffers are properly marked released on the release event so `in_use` is cleared

Example fix

// before
let buf = self.buffers[index].clone();
buf.attach(&surface); // may still be in_use

// after
let buf = self.buffers.iter().find(|b| !b.in_use.get())
    .expect("no free buffer; grow pool or wait for release");
buf.attach(&surface);
Defensive patterns

Strategy: validation

Validate before calling

if buffer_in_use(buffer) { pick_free_buffer_or_wait_for_release() } else { buffer.attach(&surface) }

Type guard

fn is_free(b: &Buffer) -> bool { !b.in_use.get() }

Prevention

When it happens

Trigger: The surface code selects a buffer from the pool that has not yet been released (e.g. all buffers busy) and calls `attach` on it a second time before the release event arrives.

Common situations: Fast resize/redraw sequences exhausting the buffer pool; a missing/compositor bug in buffer release handling causing stale `in_use` flags.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at druid-shell/src/backend/wayland/surfaces/buffers.rs:285

        let inner = pool.create_buffer(offset, width, height, stride, wl_shm::Format::Argb8888);
        let in_use = Rc::new(Cell::new(false));

        inner.quick_assign(with_cloned!(in_use; move |b, event, _dispatchdata| {
            tracing::trace!("buffer event: {:?} {:?}", b, event);
            match event {
                wl_buffer::Event::Release => {
                    in_use.set(false);
                }
                _ => tracing::warn!("unhandled wayland buffer event: {:?} {:?}", b, event),
            }
        }));

        Buffer { inner, in_use }
    }

    pub fn attach(&self, wl_surface: &wl::Main<WlSurface>) {
        if self.in_use.get() {
            panic!("attaching an already in-use surface");
        }
        self.in_use.set(true);
        wl_surface.attach(Some(&self.inner), 0, 0);
    }

    pub fn destroy(&self) {
        if self.in_use.get() {
            panic!("Destroying a buffer while it is in use");
        }
        self.inner.destroy();
    }
}

pub struct BufferData<const N: usize> {
    buffers: WeakRc<Buffers<N>>,
    mmap: Mmap,
}

View on GitHub (pinned to 0f8b1195e4)