linebender/druid · error
Failed to update cairo drawable
Error message
Failed to update cairo drawable: {} What it means
This error is raised in `update_cairo_surface` when the cairo surface refuses to switch its backing XCB drawable to the newly created pixmap for the window. It wraps the underlying cairo error (via anyhow) after `set_drawable` fails, typically because the pixmap is invalid, the connection is broken, or cairo is in an error state. It means druid-shell cannot keep the render target in sync with the X11 buffer pool, so the frame cannot be drawn.
Solutions
- Check that the X11 connection is alive and the window id is still valid (handle DestroyNotify/Disconnected events).
- Ensure resize logic does not call render with a 0-width or 0-height pixmap; guard sizes before create_pixmap.
- Update/verify cairo and druid-shell versions; cairo-level set_drawable failures can stem from cairo/xcb binding bugs.
- Reproduce under Xvfb/xtrace to inspect whether the pixmap creation itself failed before set_drawable.
Example fix
// before
borrow_mut!(self.cairo_surface)?
.set_drawable(&drawable, buffers.width as i32, buffers.height as i32)
.map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?;
// after
if buffers.width == 0 || buffers.height == 0 {
return Ok(()); // skip render for degenerate sizes
}
borrow_mut!(self.cairo_surface)?
.set_drawable(&drawable, buffers.width as i32, buffers.height as i32)
.map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?; Defensive patterns
Strategy: try-catch
Validate before calling
// before triggering a render on X11 assert!(window.is_visible(), "window destroyed; cannot update cairo surface"); let (w, h) = window.get_size(); assert!(w > 0 && h > 0, "degenerate window size");
Type guard
fn is_surface_usable(bufs: &SurfaceBuffers) -> bool {
bufs.width > 0 && bufs.height > 0 && !bufs.pixmaps.is_empty()
} Try / catch
match window.update_cairo_surface() {
Ok(()) => {},
Err(e) if e.to_string().contains("Failed to update cairo drawable") => {
log::error!("cairo drawable update failed: {} — checking X connection", e);
if !connection.is_alive() { reconnect_or_exit(); }
},
Err(e) => return Err(e),
} Prevention
- Guard renders against zero-sized windows during resize storms
- Handle X11 disconnect/destroy events before rendering
- Keep cairo and druid-shell versions in sync
- Test under Xvfb/SSH-forwarded displays before shipping
When it happens
Trigger: `Window::render` calls `update_cairo_surface` after creating/resizing pixmaps via `buffers.create_pixmap`, and `cairo_surface.set_drawable(&XCBDrawable(pixmap), width, height)` returns Err — e.g. after a window resize produces an invalid pixmap, or the X server connection died.
Common situations: Window resize races on X11, X server disconnects or crashes mid-frame, running in environments where XCB/GLX state is stale (e.g. SSH-forwarded displays, nested X servers like Xvfb with limited extensions).
Related errors
- Window::render - piet finish failed
- after rendering, no pixmap to present
- unexpected mouse wheel button
- Application is already running
- Application state already borrowed
AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10).
Data as JSON: /api/errors/0198da23b1d37c89.
Report an issue: GitHub.
Appendix: source
Thrown at druid-shell/src/backend/x11/window.rs:766
self.with_handler(|h| h.scale(scale));
}
Ok(())
}
// Ensure that our cairo context is targeting the right drawable, allocating one if necessary.
fn update_cairo_surface(&self) -> Result<(), Error> {
let mut buffers = borrow_mut!(self.buffers)?;
let pixmap = if let Some(p) = buffers.idle_pixmaps.last() {
*p
} else {
info!("ran out of idle pixmaps, creating a new one");
buffers.create_pixmap(self.app.connection(), self.id)?
};
let drawable = XCBDrawable(pixmap);
borrow_mut!(self.cairo_surface)?
.set_drawable(&drawable, buffers.width as i32, buffers.height as i32)
.map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?;
Ok(())
}
fn render(&self) -> Result<(), Error> {
self.with_handler(|h| h.prepare_paint());
if self.destroyed() {
return Ok(());
}
self.update_cairo_surface()?;
let invalid = std::mem::replace(&mut *borrow_mut!(self.invalid)?, Region::EMPTY);
{
let surface = borrow!(self.cairo_surface)?;
let cairo_ctx = cairo::Context::new(&*surface).unwrap();
let scale = self.scale.get();
for rect in invalid.rects() {
let rect = rect.to_px(scale).round();View on GitHub (pinned to 0f8b1195e4)