linebender/druid · error

called request_paint during painting

Error message

called request_paint during painting

What it means

`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.

Solutions

  1. Defer the repaint: set a needs-redraw flag and call `request_paint` after the current paint pass completes
  2. Move invalidation logic out of `paint`/draw callbacks into event handling
  3. Use the event loop's idle phase to schedule the repaint

Example fix

// before
impl Widget for W {
    fn paint(&mut self, ctx, ...) {
        ctx.request_paint(); // panics during painting
    }
}

// after
impl Widget for W {
    fn paint(&mut self, ctx, ...) {
        self.needs_repaint = true; // handled later, outside paint
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

if surface_is_painting() { schedule_repaint_after_pass(); } else { surface.request_paint(); }

Type guard

fn can_request_paint(pending_borrowed: bool) -> bool { !pending_borrowed }

Prevention

When it happens

Trigger: Calling `surface.request_paint()` re-entrantly from within a paint/draw callback, while `pending_buffer_borrowed` is `true`.

Common situations: Invalidating the widget/surface inside a `paint` implementation, or triggering redraws synchronously from drawing code.

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/ea27bfa4b20b0229. Report an issue: GitHub.

Appendix: source

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

    /// This calls into user code. To avoid re-entrancy, ensure that we are not already in user
    /// code (defer this call if necessary).
    ///
    /// We will call into `WindowData` to paint the frame, and present it. If no buffers are
    /// available we will set a flag, so that when one becomes available we immediately paint and
    /// present. This includes if we need to resize.
    pub fn request_paint(self: &Rc<Self>, window: &surface::Data) {
        tracing::trace!(
            "request_paint {:?} {:?}",
            self.size.get(),
            window.get_size()
        );
        // if our size is empty there is nothing to do.
        if self.size.get().is_empty() {
            return;
        }

        if self.pending_buffer_borrowed.get() {
            panic!("called request_paint during painting");
        }

        // recreate if necessary
        self.buffers_recreate();

        // paint if we have a buffer available.
        if self.pending_buffer_released() {
            self.paint_unchecked(window);
        }

        // attempt to release any unused buffers.
        self.buffers_drop_unused();
    }

    /// Paint the next frame, without checking if the buffer is free.
    fn paint_unchecked(self: &Rc<Self>, window: &surface::Data) {
        tracing::trace!("buffer.paint_unchecked");
        let mut buf_data = self.pending_buffer_data().unwrap();

View on GitHub (pinned to 0f8b1195e4)