a73x

b0070dd0

Harden FrameLoop per code-quality review

a73x   2026-04-16 11:03

Commit message
Harden FrameLoop per code-quality review

- commitRender: convert canRender precondition from std.debug.assert
  (compiled out in ReleaseFast) to a runtime error.CannotRender, so
  caller logic bugs are observable in release builds.
- waitForWork: propagate only .revents back to the caller's pollfd
  slice; .events and .fd are caller-owned and should be preserved.
- waitForWork doc: make the 8-extra cap rationale explicit on the
  public API.

Follow-up to dec56ed.

src/frame_loop.zig
Old New
@@ -64,7 +64,7 @@ pub const FrameLoop = struct {
64 } 64 }
65 65
66 pub fn commitRender(self: *FrameLoop) !void { 66 pub fn commitRender(self: *FrameLoop) !void {
67 std.debug.assert(self.canRender()); 67 if (!self.canRender()) return error.CannotRender;
68 if (self.pending_token) |t| self.ops.destroyCallbackFn(self.ops.ctx, t); 68 if (self.pending_token) |t| self.ops.destroyCallbackFn(self.ops.ctx, t);
69 self.pending_token = try self.ops.requestFrameFn(self.ops.ctx, self); 69 self.pending_token = try self.ops.requestFrameFn(self.ops.ctx, self);
70 self.armed = false; 70 self.armed = false;
@@ -95,6 +95,11 @@ pub const FrameLoop = struct {
95 95
96 /// Blocks on wl_fd + extra pollfds with `timeout_ms`, then reads + dispatches 96 /// Blocks on wl_fd + extra pollfds with `timeout_ms`, then reads + dispatches
97 /// any pending Wayland events. Safe to call in any state. 97 /// any pending Wayland events. Safe to call in any state.
98 ///
99 /// `extra.len` must not exceed 8. waystty's production callers never poll
100 /// more than wl_fd + one extra fd (pty); the cap of 8 is a 4x margin and
101 /// keeps the internal pollfd array stack-allocated. Increase requires a
102 /// review of the stack frame size in this function.
98 pub fn waitForWork( 103 pub fn waitForWork(
99 self: *FrameLoop, 104 self: *FrameLoop,
100 extra: []std.posix.pollfd, 105 extra: []std.posix.pollfd,
@@ -113,8 +118,9 @@ pub const FrameLoop = struct {
113 118
114 _ = std.posix.poll(all[0..total], timeout_ms) catch {}; 119 _ = std.posix.poll(all[0..total], timeout_ms) catch {};
115 120
116 // Propagate revents back into caller's extra slice. 121 // Propagate only revents back into caller's extra slice; events/fd are
117 for (extra, 0..) |*fd, i| fd.* = all[i + 1]; 122 // caller-owned and must be preserved across calls.
123 for (extra, 0..) |*fd, i| fd.revents = all[i + 1].revents;
118 124
119 if (all[0].revents & std.posix.POLL.IN != 0) { 125 if (all[0].revents & std.posix.POLL.IN != 0) {
120 if (self.ops.prepareReadFn(self.ops.ctx)) { 126 if (self.ops.prepareReadFn(self.ops.ctx)) {