a73x

9c8255f8

refactor: one bounded wait for both side-connection asks

a73x   2026-08-29 10:01

Commit message
refactor: one bounded wait for both side-connection asks

birthSession and listSessions each spelled the same eleven lines before
reading: the deadline check, the one-fd poll, the service, and the QUIC
disjunct where a frame arrives with the socket never going readable.
awaitFrames owns them, so a third ask on its own connection inherits the
disjunct instead of hanging on QUIC. What the two answer with still
differs and stays where it is — a birth reads a close as Refused, a poll
reads it as Transport.

Pinned by the wall's per-host poll legs in e2e 09_hosts and by the picker
birth in 12_panes.

src/client/client.zig
Old New
@@ -1052,6 +1052,24 @@ pub const birth_rows: u16 = 24;
1052 // the tile reads [refused], which is where it stood before. 1052 // the tile reads [refused], which is where it stood before.
1053 const birth_budget_ms: i64 = 3000; 1053 const birth_budget_ms: i64 = 3000;
1054 1054
1055 /// One bounded wait on a side connection, shared by every ask that opens
1056 /// its own: false means loop again without reading, `error.Timeout` means
1057 /// the budget is spent. No EINTR arm — `std.posix.poll` retries INTR
1058 /// itself, and its error set holds only failures the fd never recovers
1059 /// from, so waiting the deadline out on one would be a stall, not a wait.
1060 fn awaitFrames(tr: *Transport, deadline: i64) !bool {
1061 const left = deadline - std.time.milliTimestamp();
1062 if (left <= 0) return error.Timeout;
1063 var fds = [_]std.posix.pollfd{
1064 .{ .fd = tr.pollFd(), .events = std.posix.POLL.IN, .revents = 0 },
1065 };
1066 _ = std.posix.poll(&fds, tr.timeoutMs(@intCast(@min(left, 100)))) catch
1067 return error.Transport;
1068 tr.service();
1069 // Over QUIC a frame can arrive with the socket never going readable.
1070 return fds[0].revents != 0 or tr.link == .quic;
1071 }
1072
1055 /// Creates `name` on `target` over a connection of its own, then leaves. 1073 /// Creates `name` on `target` over a connection of its own, then leaves.
1056 pub fn birthSession( 1074 pub fn birthSession(
1057 alloc: std.mem.Allocator, 1075 alloc: std.mem.Allocator,
@@ -1083,20 +1101,7 @@ pub fn birthSession(
1083 try tr.writeFrame(.attach, proto.encodeAttachNamed(&buf, cols, rows, 0, 0, proto.wireName(name))); 1101 try tr.writeFrame(.attach, proto.encodeAttachNamed(&buf, cols, rows, 0, 0, proto.wireName(name)));
1084 const deadline = std.time.milliTimestamp() + birth_budget_ms; 1102 const deadline = std.time.milliTimestamp() + birth_budget_ms;
1085 while (true) { 1103 while (true) {
1086 const left = deadline - std.time.milliTimestamp(); 1104 if (!try awaitFrames(&tr, deadline)) continue;
1087 if (left <= 0) return error.Timeout;
1088 var fds = [_]std.posix.pollfd{
1089 .{ .fd = tr.pollFd(), .events = std.posix.POLL.IN, .revents = 0 },
1090 };
1091 // No EINTR arm: `std.posix.poll` retries INTR itself, and its
1092 // error set holds only failures the fd never recovers from —
1093 // waiting the deadline out on one would be a stall, not a wait.
1094 _ = std.posix.poll(&fds, tr.timeoutMs(@intCast(@min(left, 100)))) catch
1095 return error.Transport;
1096 tr.service();
1097 // The pump's own disjunct: over QUIC a frame can arrive with the
1098 // socket never going readable.
1099 if (fds[0].revents == 0 and tr.link != .quic) continue;
1100 while (true) { 1105 while (true) {
1101 // Not `Refused`: a read that failed is a transport that 1106 // Not `Refused`: a read that failed is a transport that
1102 // broke, and telling the browser its attach was refused for 1107 // broke, and telling the browser its attach was refused for
@@ -1163,17 +1168,7 @@ pub fn listSessions(
1163 try tr.writeFrame(.sessions_req, ""); 1168 try tr.writeFrame(.sessions_req, "");
1164 const deadline = std.time.milliTimestamp() + budget_ms; 1169 const deadline = std.time.milliTimestamp() + budget_ms;
1165 while (true) { 1170 while (true) {
1166 const left = deadline - std.time.milliTimestamp(); 1171 if (!try awaitFrames(&tr, deadline)) continue;
1167 if (left <= 0) return error.Timeout;
1168 var fds = [_]std.posix.pollfd{
1169 .{ .fd = tr.pollFd(), .events = std.posix.POLL.IN, .revents = 0 },
1170 };
1171 _ = std.posix.poll(&fds, tr.timeoutMs(@intCast(@min(left, 100)))) catch
1172 return error.Transport;
1173 tr.service();
1174 // `birthSession`'s disjunct: over QUIC a frame can arrive with the
1175 // socket never going readable.
1176 if (fds[0].revents == 0 and tr.link != .quic) continue;
1177 while (true) { 1172 while (true) {
1178 switch (tr.readFrame(alloc) catch |e| return oomOrTransport(e)) { 1173 switch (tr.readFrame(alloc) catch |e| return oomOrTransport(e)) {
1179 .incomplete => break, 1174 .incomplete => break,