449f80eb
refactor: one seating tail for both attach arms
a73x 2026-08-29 10:01
Commit message
src/server/server.zig
| Old | New | ||
|---|---|---|---|
| @@ -2036,9 +2036,6 @@ pub const Server = struct { | |||
| 2036 | _ = self.queueFrame(i, .exit_status, &.{1}); | 2036 | _ = self.queueFrame(i, .exit_status, &.{1}); |
| 2037 | return; | 2037 | return; |
| 2038 | }; | 2038 | }; |
| 2039 | // Past the refusal, so the counter never grows for an | ||
| 2040 | // attach that attached nobody. See upgrade.Counters.attaches. | ||
| 2041 | self.stats.attaches += 1; | ||
| 2042 | if (self.clients[i]) |*c| { | 2039 | if (self.clients[i]) |*c| { |
| 2043 | // An await's since_seq is a watermark in the OLD | 2040 | // An await's since_seq is a watermark in the OLD |
| 2044 | // session's tracker, and seq series are per-session — | 2041 | // session's tracker, and seq series are per-session — |
| @@ -2052,12 +2049,20 @@ pub const Server = struct { | |||
| 2052 | if (c.session != si) c.await_state = null; | 2049 | if (c.session != si) c.await_state = null; |
| 2053 | c.session = si; | 2050 | c.session = si; |
| 2054 | } | 2051 | } |
| 2052 | self.seatClient(i, si, req); | ||
| 2053 | } | ||
| 2054 | |||
| 2055 | /// The seating both attach arms end on, past every refusal so `attaches` | ||
| 2056 | /// never grows for an attach that seated nobody. Size is recorded only | ||
| 2057 | /// when the grid really went there (a refused one leaves the slot 0x0), | ||
| 2058 | /// modes go ahead of the state they describe, and only a size change | ||
| 2059 | /// repaints anyone but the joiner. | ||
| 2060 | fn seatClient(self: *Server, i: usize, si: usize, req: proto.AttachReq) void { | ||
| 2061 | self.stats.attaches += 1; | ||
| 2055 | self.bumpActivity(i); | 2062 | self.bumpActivity(i); |
| 2056 | const size_changed = (req.cols != self.colsNow(si) or req.rows != self.rowsNow(si)); | 2063 | const size_changed = (req.cols != self.colsNow(si) or req.rows != self.rowsNow(si)); |
| 2057 | const applied = self.applySize(si, req.cols, req.rows); | 2064 | const applied = self.applySize(si, req.cols, req.rows); |
| 2058 | if (applied) self.recordSize(si, i); | 2065 | if (applied) self.recordSize(si, i); |
| 2059 | // Ahead of the state it describes, so a client can never be | ||
| 2060 | // holding grid content it has no mode for. | ||
| 2061 | self.sendPtyModeTo(si, i); | 2066 | self.sendPtyModeTo(si, i); |
| 2062 | self.sendResync(si, i, req.have_seq, req.have_epoch, size_changed and applied); | 2067 | self.sendResync(si, i, req.have_seq, req.have_epoch, size_changed and applied); |
| 2063 | self.sendCmdStateTo(si, i); | 2068 | self.sendCmdStateTo(si, i); |
| @@ -2506,33 +2511,11 @@ pub const Server = struct { | |||
| 2506 | const si = self.sessions.resolve(self, sz.name, sz.cols, sz.rows) orelse { | 2511 | const si = self.sessions.resolve(self, sz.name, sz.cols, sz.rows) orelse { |
| 2507 | return self.refuseObserver(i); | 2512 | return self.refuseObserver(i); |
| 2508 | }; | 2513 | }; |
| 2509 | // Past both refusals above, so the counter only ever grows | ||
| 2510 | // for an attach that seated somebody. See upgrade.Counters.attaches. | ||
| 2511 | self.stats.attaches += 1; | ||
| 2512 | // Promote: the buffer moves with the fd. | 2514 | // Promote: the buffer moves with the fd. |
| 2513 | const moved = self.observers[i].?.inbound; | 2515 | const moved = self.observers[i].?.inbound; |
| 2514 | self.observers[i] = null; | 2516 | self.observers[i] = null; |
| 2515 | self.clients[slot] = .{ .sink = .{ .socket = fd }, .session = si, .inbound = moved }; | 2517 | self.clients[slot] = .{ .sink = .{ .socket = fd }, .session = si, .inbound = moved }; |
| 2516 | // The other half of the .attach arm's bump: a socket | 2518 | self.seatClient(slot, si, sz); |
| 2517 | // client's FIRST attach lands here, not in handleFrame, so | ||
| 2518 | // leaving it out would seat every fresh CLI client at 0 and | ||
| 2519 | // order them all identically until somebody typed. | ||
| 2520 | self.bumpActivity(slot); | ||
| 2521 | const size_changed = (sz.cols != self.colsNow(si) or sz.rows != self.rowsNow(si)); | ||
| 2522 | const applied = self.applySize(si, sz.cols, sz.rows); | ||
| 2523 | // The slot's size is what latest-wins re-reads when this | ||
| 2524 | // client types, so it is set here and kept current by the | ||
| 2525 | // .resize/.attach arms — but only when the grid really went | ||
| 2526 | // there. A refused attach must leave the slot at 0x0. | ||
| 2527 | if (applied) self.recordSize(si, slot); | ||
| 2528 | self.sendPtyModeTo(si, slot); | ||
| 2529 | // Latest wins: a size change broadcasts, repainting every | ||
| 2530 | // client at the new attacher's size. A same-size join is | ||
| 2531 | // the joiner's business alone — see sendResync. So is a | ||
| 2532 | // refused one: the size differs but the grid never moved, | ||
| 2533 | // so there is nothing to repaint anyone else for. | ||
| 2534 | self.sendResync(si, slot, sz.have_seq, sz.have_epoch, size_changed and applied); | ||
| 2535 | self.sendCmdStateTo(si, slot); | ||
| 2536 | // Frames the same write carried behind the attach (a wall | 2519 | // Frames the same write carried behind the attach (a wall |
| 2537 | // tile sends agent_offer on its heels) are handled now, as | 2520 | // tile sends agent_offer on its heels) are handled now, as |
| 2538 | // the client they were addressed to. | 2521 | // the client they were addressed to. |