git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit3c4da0f982867818e3801059f5c16b7740d49417
parentae7c0cf727
authorIsaac Freund <[email protected]>
date2025-12-15 12:00
WindowManager: fix possible assertion failure

It's possible for renderStart() to be called due to all tracked
configures being completed while at the same time a dirtyIdle() callback
from a dirtyRendering() call is waiting to be run. This results in the
assertion in dirtyIdle() failing.

 river/WindowManager.zig | 43 +++++++++++++++++++++++++++++--------------
 1 file changed, 29 insertions(+), 14 deletions(-)

diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index f3a2c12..e2cca60 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -227,19 +227,26 @@ pub fn ensureRendering(wm: *WindowManager) bool {
 
 pub fn dirtyWindowing(wm: *WindowManager) void {
     wm.scheduled.dirty = true;
+    wm.addDirtyIdle();
+}
 
-    if (wm.dirty_idle == null) {
-        const event_loop = server.wl_server.getEventLoop();
-        wm.dirty_idle = event_loop.addIdle(*WindowManager, dirtyIdle, wm) catch {
-            log.err("out of memory", .{});
-            return;
-        };
-    }
+pub fn cleanWindowing(wm: *WindowManager) void {
+    wm.scheduled.dirty = false;
+    wm.removeDirtyIdle();
 }
 
 pub fn dirtyRendering(wm: *WindowManager) void {
     wm.rendering_scheduled.dirty = true;
+    wm.addDirtyIdle();
+}
+
+pub fn cleanRendering(wm: *WindowManager) void {
+    wm.rendering_scheduled.dirty = false;
+    wm.removeDirtyIdle();
+}
 
+fn addDirtyIdle(wm: *WindowManager) void {
+    assert(wm.scheduled.dirty or wm.rendering_scheduled.dirty);
     if (wm.dirty_idle == null) {
         const event_loop = server.wl_server.getEventLoop();
         wm.dirty_idle = event_loop.addIdle(*WindowManager, dirtyIdle, wm) catch {
@@ -249,6 +256,15 @@ pub fn dirtyRendering(wm: *WindowManager) void {
     }
 }
 
+fn removeDirtyIdle(wm: *WindowManager) void {
+    if (!wm.scheduled.dirty and !wm.rendering_scheduled.dirty) {
+        if (wm.dirty_idle) |event_source| {
+            event_source.remove();
+            wm.dirty_idle = null;
+        }
+    }
+}
+
 fn dirtyIdle(wm: *WindowManager) void {
     assert(wm.scheduled.dirty or wm.rendering_scheduled.dirty);
     wm.dirty_idle = null;
@@ -267,6 +283,8 @@ fn dirtyIdle(wm: *WindowManager) void {
 fn manageStart(wm: *WindowManager) void {
     assert(wm.state == .idle);
     assert(wm.scheduled.dirty);
+    wm.cleanWindowing();
+    wm.state = .manage;
 
     log.debug("manage sequence start", .{});
 
@@ -302,9 +320,6 @@ fn manageStart(wm: *WindowManager) void {
         while (it.next()) |seat| seat.manageStart();
     }
 
-    wm.scheduled.dirty = false;
-    wm.state = .manage;
-
     if (wm.object) |wm_v1| {
         wm_v1.sendManageStart();
     } else {
@@ -379,7 +394,10 @@ pub fn notifyConfigured(wm: *WindowManager) void {
 }
 
 fn renderStart(wm: *WindowManager) void {
-    assert(wm.state == .idle or wm.state.inflight_configures == 0);
+    assert((wm.state == .idle and wm.rendering_scheduled.dirty) or
+        wm.state.inflight_configures == 0);
+    wm.state = .render;
+    wm.cleanRendering();
 
     log.debug("render sequence start", .{});
 
@@ -393,9 +411,6 @@ fn renderStart(wm: *WindowManager) void {
         }
     }
 
-    wm.state = .render;
-    wm.rendering_scheduled.dirty = false;
-
     if (wm.object) |wm_v1| {
         wm_v1.sendRenderStart();
     } else {