From 9d9dbc65e08ba239326ba25965493ffc5f354750 Mon Sep 17 00:00:00 2001 From: "Johannes C. Mayer" Date: Tue, 28 Apr 2026 13:29:29 +0200 Subject: [PATCH] wayland: treat flush WouldBlock as recoverable back-pressure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the kernel socket send buffer (`SO_SNDBUF`) saturates under client write bursts, `wl_display.flush()` returns `WaylandError::Io(WouldBlock)`. libwayland documents this as recoverable: poll(POLLOUT) on the display fd and retry on the next iteration. Previously every flush error — including this transient back-pressure case — was treated as fatal and called `set_exit_code(1)`, killing the event loop on inputs that should have been absorbed naturally. Match on the flush result, ignore `Io(WouldBlock)`, propagate other errors. Mirrors the pattern in Smithay/calloop-wayland-source's flush_queue, which already handles this distinction correctly. Reproduces with high client→server request bursts (e.g. ~4000 xdg_toplevel.set_title/set_size requests in 60ms from multiple threads). After the fix, the same workload succeeds without exiting the loop. --- winit-wayland/src/event_loop/mod.rs | 18 +++++++++++++++--- winit/src/changelog/unreleased.md | 5 +++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 3a6ca6982b..683fc7e792 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -264,9 +264,21 @@ impl EventLoop { // // Checking for flush error is essential to perform an exit with error, since // once we have a protocol error, we could get stuck retrying... - if self.handle.connection.flush().is_err() { - self.set_exit_code(1); - return; + match self.handle.connection.flush() { + Ok(()) => {}, + Err(sctk::reexports::client::backend::WaylandError::Io(e)) + if e.kind() == std::io::ErrorKind::WouldBlock => + { + // EAGAIN/WouldBlock from the kernel socket: SO_SNDBUF is full. + // libwayland documents this as recoverable back-pressure — the + // poll(POLLOUT) on the next loop iteration will drain the buffer + // and we can retry. Mirrors the pattern in + // calloop-wayland-source's `flush_queue`. + }, + Err(_) => { + self.set_exit_code(1); + return; + }, } if let Err(error) = self.loop_dispatch(timeout) { diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 729bfd639b..bf7b45d4c3 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -61,3 +61,8 @@ changelog entry. - On Wayland, switch from using the `ahash` hashing algorithm to `foldhash`. - On macOS, fix borderless game presentation options not sticking after switching spaces. - On macOS, fix IME being locked on (regardless of requests to disable) after being enabled once. +- On Wayland, treat `wl_display.flush()` returning `WouldBlock` as + recoverable back-pressure rather than a fatal error. Previously the + event loop would exit with code 1 on transient kernel send-buffer + saturation; it now retries on the next loop iteration as libwayland + documents.