{"record":{"id":"ea27bfa4b20b0229","repo":"linebender/druid","slug":"called-request-paint-during-painting","errorCode":null,"errorMessage":"called request_paint during painting","messagePattern":"called request_paint during painting","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"druid-shell/src/backend/wayland/surfaces/buffers.rs","lineNumber":115,"sourceCode":"    /// This calls into user code. To avoid re-entrancy, ensure that we are not already in user\n    /// code (defer this call if necessary).\n    ///\n    /// We will call into `WindowData` to paint the frame, and present it. If no buffers are\n    /// available we will set a flag, so that when one becomes available we immediately paint and\n    /// present. This includes if we need to resize.\n    pub fn request_paint(self: &Rc<Self>, window: &surface::Data) {\n        tracing::trace!(\n            \"request_paint {:?} {:?}\",\n            self.size.get(),\n            window.get_size()\n        );\n        // if our size is empty there is nothing to do.\n        if self.size.get().is_empty() {\n            return;\n        }\n\n        if self.pending_buffer_borrowed.get() {\n            panic!(\"called request_paint during painting\");\n        }\n\n        // recreate if necessary\n        self.buffers_recreate();\n\n        // paint if we have a buffer available.\n        if self.pending_buffer_released() {\n            self.paint_unchecked(window);\n        }\n\n        // attempt to release any unused buffers.\n        self.buffers_drop_unused();\n    }\n\n    /// Paint the next frame, without checking if the buffer is free.\n    fn paint_unchecked(self: &Rc<Self>, window: &surface::Data) {\n        tracing::trace!(\"buffer.paint_unchecked\");\n        let mut buf_data = self.pending_buffer_data().unwrap();","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/linebender/druid/blob/0f8b1195e4e073f9597f2865299c3d18f8e4005f/druid-shell/src/backend/wayland/surfaces/buffers.rs#L97-L133","documentation":"`request_paint` panics if the surface's buffer is currently borrowed for painting (`pending_buffer_borrowed`). Requesting a new paint while a paint pass is already in progress would corrupt buffer state, so it is an explicit invariant violation.","triggerScenarios":"Calling `surface.request_paint()` re-entrantly from within a paint/draw callback, while `pending_buffer_borrowed` is `true`.","commonSituations":"Invalidating the widget/surface inside a `paint` implementation, or triggering redraws synchronously from drawing code.","solutions":["Defer the repaint: set a needs-redraw flag and call `request_paint` after the current paint pass completes","Move invalidation logic out of `paint`/draw callbacks into event handling","Use the event loop's idle phase to schedule the repaint"],"exampleFix":"// before\nimpl Widget for W {\n    fn paint(&mut self, ctx, ...) {\n        ctx.request_paint(); // panics during painting\n    }\n}\n\n// after\nimpl Widget for W {\n    fn paint(&mut self, ctx, ...) {\n        self.needs_repaint = true; // handled later, outside paint\n    }\n}","handlingStrategy":"type-guard","validationCode":"if surface_is_painting() { schedule_repaint_after_pass(); } else { surface.request_paint(); }","typeGuard":"fn can_request_paint(pending_borrowed: bool) -> bool { !pending_borrowed }","tryCatchPattern":null,"preventionTips":["Never invalidate/redraw from inside paint or draw callbacks","Defer repaint requests via a dirty flag processed outside the paint pass","Audit widget code for ctx.request_paint() calls in paint impls"],"tags":["wayland","rendering","reentrancy","buffers"],"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"}