{"record":{"id":"3aed0bbbba32eaf5","repo":"linebender/druid","slug":"attaching-an-already-in-use-surface","errorCode":null,"errorMessage":"attaching an already in-use surface","messagePattern":"attaching an already in-use surface","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"druid-shell/src/backend/wayland/surfaces/buffers.rs","lineNumber":285,"sourceCode":"        let inner = pool.create_buffer(offset, width, height, stride, wl_shm::Format::Argb8888);\n        let in_use = Rc::new(Cell::new(false));\n\n        inner.quick_assign(with_cloned!(in_use; move |b, event, _dispatchdata| {\n            tracing::trace!(\"buffer event: {:?} {:?}\", b, event);\n            match event {\n                wl_buffer::Event::Release => {\n                    in_use.set(false);\n                }\n                _ => tracing::warn!(\"unhandled wayland buffer event: {:?} {:?}\", b, event),\n            }\n        }));\n\n        Buffer { inner, in_use }\n    }\n\n    pub fn attach(&self, wl_surface: &wl::Main<WlSurface>) {\n        if self.in_use.get() {\n            panic!(\"attaching an already in-use surface\");\n        }\n        self.in_use.set(true);\n        wl_surface.attach(Some(&self.inner), 0, 0);\n    }\n\n    pub fn destroy(&self) {\n        if self.in_use.get() {\n            panic!(\"Destroying a buffer while it is in use\");\n        }\n        self.inner.destroy();\n    }\n}\n\npub struct BufferData<const N: usize> {\n    buffers: WeakRc<Buffers<N>>,\n    mmap: Mmap,\n}\n","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/linebender/druid/blob/0f8b1195e4e073f9597f2865299c3d18f8e4005f/druid-shell/src/backend/wayland/surfaces/buffers.rs#L267-L303","documentation":"`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.","triggerScenarios":"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.","commonSituations":"Fast resize/redraw sequences exhausting the buffer pool; a missing/compositor bug in buffer release handling causing stale `in_use` flags.","solutions":["Increase the buffer pool size so a free buffer is always available","Wait for buffer release (frame callbacks / `wl_buffer.release`) before reattaching","Ensure buffers are properly marked released on the release event so `in_use` is cleared"],"exampleFix":"// before\nlet buf = self.buffers[index].clone();\nbuf.attach(&surface); // may still be in_use\n\n// after\nlet buf = self.buffers.iter().find(|b| !b.in_use.get())\n    .expect(\"no free buffer; grow pool or wait for release\");\nbuf.attach(&surface);","handlingStrategy":"validation","validationCode":"if buffer_in_use(buffer) { pick_free_buffer_or_wait_for_release() } else { buffer.attach(&surface) }","typeGuard":"fn is_free(b: &Buffer) -> bool { !b.in_use.get() }","tryCatchPattern":null,"preventionTips":["Always select buffers via a free-buffer check before attach","Size the buffer pool generously for high-frequency redraws and resizes","Handle `wl_buffer.release` promptly so `in_use` flags clear correctly"],"tags":["wayland","buffers","panic","rendering"],"backgroundTag":"invalid-state-transition","analyzedSha":"0f8b1195e4e073f9597f2865299c3d18f8e4005f","analyzedAt":"2026-09-10T14:27:34.582Z","contentChangedAt":"2026-09-10T14:27:34.582Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}