a73x

fcf6547b

refactor: the daemon's comments state the rule, not its provenance

a73x   2026-08-30 19:24

Commit message
refactor: the daemon's comments state the rule, not its provenance

server.zig's 109 essays are 17. The paragraphs that survived name what
breaks; the ones that narrated which commit changed what, which test
found it, and what an earlier cut assumed are gone — a reader fixing this
file cannot act on any of it.

3622 -> 3162 lines, 1416 -> 956 comment lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XSUFuYHqU9wr4J5NC8EkWV

docscheck.blocks
Old New
@@ -37,7 +37,7 @@ server_test_modes.zig 11
37 server_test_quic.zig 6 37 server_test_quic.zig 6
38 server_test_session.zig 18 38 server_test_session.zig 18
39 server_test_upgrade.zig 1 39 server_test_upgrade.zig 1
40 server.zig 108 40 server.zig 17
41 shellint.zig 7 41 shellint.zig 7
42 sockpath.zig 5 42 sockpath.zig 5
43 spawn.zig 0 43 spawn.zig 0
src/server/server.zig
Old New
@@ -1,13 +1,9 @@
1 //! The daemon core (`mux d start`): up to max_sessions sessions (engine + pty + command 1 //! The daemon core (`mux d start`): up to `max_sessions` sessions, each an
2 //! tracker each); a connection is a session, named at attach. One listener 2 //! engine, a pty and a command tracker, named at attach. One listener on the
3 //! on the unix socket, and a second on UDP when QUIC is configured. 3 //! unix socket and a second on UDP when QUIC is configured. Every state
4 //! Up to max_clients attached interactive clients plus a few one-shot 4 //! update broadcasts to that session's clients, and each grid follows its
5 //! observer connections (dump, stats, status, endpoint, stop). Every state 5 //! most recently active one (latest wins). Single-threaded: `pumpOnce` is one
6 //! update is broadcast to that session's attached clients; each grid follows 6 //! poll iteration, so tests can drive the loop.
7 //! its most recently active client — typing, attaching or resizing claims
8 //! it (latest wins).
9 //! Single-threaded; pumpOnce is one poll iteration so tests can drive the
10 //! loop.
11 const std = @import("std"); 7 const std = @import("std");
12 const Engine = @import("term").engine.Engine; 8 const Engine = @import("term").engine.Engine;
13 const Pty = @import("pty").Pty; 9 const Pty = @import("pty").Pty;
@@ -46,12 +42,9 @@ pub const Observer = struct {
46 since_ms: i64, 42 since_ms: i64,
47 }; 43 };
48 44
49 /// How long an observer may hold a slot without completing a frame. A 45 /// How long an observer may hold a slot without completing a frame. Without
50 /// one-read-then-silence peer no longer parks the daemon, but it would 46 /// it, four silent peers close every later attach at accept with no
51 /// hold one of `max_observers` slots for good — four of them and every 47 /// diagnostic. 10 s is for a `--via` relay's first frame over a slow link.
52 /// later attach is closed at accept with no diagnostic. Local tools
53 /// speak within milliseconds; 10 s is for a `--via` relay's first frame
54 /// crossing a slow link.
55 pub const observer_idle_ms_default: i64 = 10_000; 48 pub const observer_idle_ms_default: i64 = 10_000;
56 49
57 /// The clock the daemon's bounded deadlines measure against: a calendar 50 /// The clock the daemon's bounded deadlines measure against: a calendar
@@ -61,12 +54,9 @@ pub fn monoMs() i64 {
61 return @as(i64, t.sec) * 1000 + @divFloor(t.nsec, 1_000_000); 54 return @as(i64, t.sec) * 1000 + @divFloor(t.nsec, 1_000_000);
62 } 55 }
63 56
64 /// Observer frames one pump may answer. A single 64 KB read can hold 57 /// Observer frames one pump may answer. A 64 KB read can hold thousands of
65 /// thousands of five-byte `sessions_req`s, and each is answered with up to 58 /// five-byte `sessions_req`s, so without a cap a batching peer decides how
66 /// `sessions_text_len` bytes from the daemon's ONE loop — so without a cap 59 /// long every session on the box goes dark. The rest drain next pump.
67 /// a batching peer decides how long every session on the box goes dark.
68 /// The rest wait in the buffer and are drained by the next pump, which
69 /// does not wait for the peer to write again.
70 pub const max_observer_frames_per_pump: usize = 16; 60 pub const max_observer_frames_per_pump: usize = 16;
71 61
72 /// What one observer may hold part-received. Sized to the traffic this 62 /// What one observer may hold part-received. Sized to the traffic this
@@ -75,17 +65,10 @@ pub const max_observer_frames_per_pump: usize = 16;
75 /// `max_payload`, the 16 MB paste ceiling of the CLIENT path. 65 /// `max_payload`, the 16 MB paste ceiling of the CLIENT path.
76 pub const observer_inbound_max: usize = 8 * 1024; 66 pub const observer_inbound_max: usize = 8 * 1024;
77 67
78 /// Consecutive drain wakeups that may retire nothing before drainPending 68 /// Consecutive drain wakeups that may retire nothing before `drainPending`
79 /// concludes the peer is stuck. 69 /// concludes the peer is stuck. One proves nothing — a coalesced or
80 /// 70 /// duplicate ack, or a bare flow-control update, wakes the loop without
81 /// One unproductive wakeup proves nothing, which is what the single-shot 71 /// retiring a byte. Bounded, so a stuck-writable socket cannot spin here.
82 /// version of this got wrong: an acknowledgement can arrive coalesced with
83 /// others, can re-acknowledge packets already acknowledged, or can carry
84 /// nothing but a flow-control update. Any of those wakes the loop without
85 /// retiring a byte, and treating that as "the peer is finished" abandoned
86 /// data the peer was still going to take. Bounded rather than removed,
87 /// because a socket stuck writable must not be able to spin here until the
88 /// deadline.
89 const max_drain_stalls = 64; 72 const max_drain_stalls = 64;
90 73
91 /// The bound is on a RUN of unproductive wakeups, not their total: a slow peer 74 /// The bound is on a RUN of unproductive wakeups, not their total: a slow peer
@@ -168,14 +151,9 @@ pub fn installSignalHandlers() void {
168 proxy.ignoreSigpipe(); 151 proxy.ignoreSigpipe();
169 } 152 }
170 153
171 /// Where one client's bytes go, and where they come from. 154 /// Where one client's bytes go, and where they come from. A QUIC client
172 /// 155 /// shares ONE UDP socket with every other peer and is named by connection
173 /// It used to be a bare fd, which was the assumption the whole daemon was 156 /// ID, so "the client's fd" is not something that can be polled or sent to.
174 /// built on: `clients[i].fd` went straight into the poll array, and
175 /// send/close reached it directly. A QUIC client shares ONE UDP socket with
176 /// every other peer and is identified by connection ID, so "the client's
177 /// fd" stops being something that can be polled or sent to. Breaking that
178 /// assumption is the entire point of this union.
179 const Sink = union(enum) { 157 const Sink = union(enum) {
180 socket: std.posix.fd_t, 158 socket: std.posix.fd_t,
181 /// A QUIC peer: the listener that owns the shared UDP socket, plus the 159 /// A QUIC peer: the listener that owns the shared UDP socket, plus the
@@ -233,11 +211,9 @@ const ClientSlot = struct {
233 /// socket client reads whole frames straight off its fd and never 211 /// socket client reads whole frames straight off its fd and never
234 /// buffers here; a QUIC client's stream chunks land and wait here. 212 /// buffers here; a QUIC client's stream chunks land and wait here.
235 inbound: std.ArrayList(u8) = .empty, 213 inbound: std.ArrayList(u8) = .empty,
236 /// The grid size this client last asked for *and got*: written only 214 /// The grid size this client last asked for *and got*: written only after
237 /// after an applySize that succeeded, so it is never some other 215 /// an `applySize` that succeeded, so it is never another client's size.
238 /// client's size. 0x0 until the first accepted attach/resize, which 216 /// 0x0 until the first accepted attach, which `claimGrid` reads as no claim.
239 /// claimGrid reads as "makes no claim". Latest-wins re-reads this on
240 /// every keystroke — see the `.input` arm.
241 cols: u16 = 0, 217 cols: u16 = 0,
242 rows: u16 = 0, 218 rows: u16 = 0,
243 /// Frames queued but not yet accepted by the kernel. Bounded by 219 /// Frames queued but not yet accepted by the kernel. Bounded by
@@ -249,16 +225,12 @@ const ClientSlot = struct {
249 /// be inventing a use case). 225 /// be inventing a use case).
250 await_state: ?AwaitState = null, 226 await_state: ?AwaitState = null,
251 /// Which session this client is attached to; null between a QUIC 227 /// Which session this client is attached to; null between a QUIC
252 /// handshake-promotion and its first attach. A session-less slot 228 /// handshake-promotion and its first attach. Only ever the INDEX — the
253 /// receives nothing and its session-scoped verbs go unanswered — an 229 /// wire name dies with the frame it rode in.
254 /// attach (each one re-resolves its name) is what fills this, and only
255 /// ever with the INDEX: the wire name dies with the frame it rode in.
256 session: ?usize = null, 230 session: ?usize = null,
257 /// Where this client sits in the daemon's activity order — see 231 /// Where this client sits in the daemon's activity order — see
258 /// Server.activity_clock and bumpActivity. Still 0 means never attached, 232 /// `Server.activity_clock`. Still 0 means never attached, which is also
259 /// which a QUIC slot really is between its handshake promotion and its 233 /// what a promoted-but-unattached QUIC slot's null `session` says.
260 /// first attach; that slot has no `session` either, so anything ranking
261 /// clients gets the same "not here yet" answer from both fields.
262 activity: u64 = 0, 234 activity: u64 = 0,
263 /// This client volunteered an SSH agent (`.agent_offer`). Opt-in and 235 /// This client volunteered an SSH agent (`.agent_offer`). Opt-in and
264 /// per-connection: a redial is a new slot and must offer again. 236 /// per-connection: a redial is a new slot and must offer again.
@@ -274,65 +246,40 @@ const AwaitState = struct {
274 /// milliTimestamp at acceptance; timeout measures from here. 246 /// milliTimestamp at acceptance; timeout measures from here.
275 started_ms: i64, 247 started_ms: i64,
276 /// pgid fallback edge detector: set once the fg pgid has been seen off 248 /// pgid fallback edge detector: set once the fg pgid has been seen off
277 /// the shell, so "back on the shell" means returned, not never-left. 249 /// the shell, so "back on the shell" means returned, not never-left. It
278 /// 250 /// cannot say WHICH command returned — that needs marks.
279 /// Note what this does NOT distinguish: an await issued while some
280 /// earlier command was already running answers on that command's tail,
281 /// not on a command the client started. That is still honest against
282 /// what the pgid can actually claim — "a foreground job has returned
283 /// since you asked" — and the null exit code keeps it from being read
284 /// as more. A client that needs "the command I started" needs marks.
285 saw_busy: bool = false, 251 saw_busy: bool = false,
286 }; 252 };
287 253
288 // Matches `wallview.max_tiles`: the 4 was surface-bounding (decisions.md, 254 // Matches `wallview.max_tiles`. 32 NAMEABLE sessions to cycle through, not
289 // 2026-08-25), never a measured limit. What it buys is 32 NAMEABLE 255 // 32 watched at once: every tile is its own client slot, and `max_clients`
290 // sessions to cycle through, not 32 watched at once — every tile is its 256 // still refuses the ninth.
291 // own attach and so its own client slot, and `max_clients` still refuses
292 // the ninth.
293 pub const max_sessions = 32; 257 pub const max_sessions = 32;
294 258
295 /// The smallest grid a session may exist at. Two owners used to decide 259 /// The smallest grid a session may exist at, read by `resolveSession` and
296 /// this and disagreed: resolveSession created at anything nonzero while 260 /// `applySize` alike so a 1x1 attach cannot spawn a session no resize will
297 /// applySize refused anything under 2, so a 1x1 attach could spawn a 261 /// move. It lives in protocol.zig because the client's stripe floor is the
298 /// session no resize would ever move. One constant, read by both, is what 262 /// same contract on the other end of the wire.
299 /// makes "a size a session can live at" a single fact rather than a
300 /// coincidence between two comparisons. The value itself lives in
301 /// protocol.zig: the client's stripe floor is the same contract on the
302 /// other end of the wire, and two spellings of one floor is two chances
303 /// for a thin stripe to freeze.
304 pub const min_session_cols = proto.min_session_cols; 263 pub const min_session_cols = proto.min_session_cols;
305 pub const min_session_rows = proto.min_session_rows; 264 pub const min_session_rows = proto.min_session_rows;
306 265
307 /// One shell and everything the daemon knows about it. What used to be 266 /// One shell and everything the daemon knows about it. The command tracker
308 /// daemon-global state, one level down — because "the daemon's session" 267 /// lives here and not on `Server` for the same reason the engine does: marks
309 /// stopped being a definite article when the wall wanted the same host 268 /// are one shell's lifecycle, and two shells would interleave into nonsense.
310 /// twice. The command tracker lives here and not on Server for the same
311 /// reason the engine does: marks are one shell's lifecycle, and a tracker
312 /// fed by two shells would interleave them into nonsense.
313 pub const Session = struct { 269 pub const Session = struct {
314 eng: *Engine, 270 eng: *Engine,
315 pty: Pty, 271 pty: Pty,
316 /// Row-level change tracking behind the delta stream. 272 /// Row-level change tracking behind the delta stream.
317 tracker: DeltaTracker = .{}, 273 tracker: DeltaTracker = .{},
318 /// Identifies this session instance in every snapshot it sends. Seqs are 274 /// Identifies this session instance in every snapshot it sends. Seqs mean
319 /// only meaningful within one instance: a restarted daemon counts from 275 /// nothing across instances: a client quoting a dead epoch must be
320 /// zero over different content, so a reattach quoting a pre-restart 276 /// snapshotted, never delta-served content it has never seen. Never 0 —
321 /// have_seq must be snapshotted, not deltaed. Per-session for the same 277 /// that is a client saying "I hold nothing".
322 /// reason: a recreated same-name session is a new instance, so a client
323 /// quoting the dead one's epoch resyncs by snapshot rather than being
324 /// delta-served content it has never seen. Never 0 — that value is
325 /// reserved for a client saying "I hold nothing".
326 epoch: u64, 278 epoch: u64,
327 /// This session's agent socket, listening, or -1 and null when the 279 /// This session's agent socket, or -1 when the daemon has no agent
328 /// daemon has no agent directory (or could not bind under it). One 280 /// directory. One listener per SESSION: the socket's path IS that
329 /// listener per session rather than one per daemon: the socket's path 281 /// session's `SSH_AUTH_SOCK`, and an agent connection says nothing else
330 /// IS the session's `SSH_AUTH_SOCK`, so a shared one could not tell 282 /// about who is calling. -1 rather than optional, like every other fd here.
331 /// which session a connecting client belonged to, and there is nothing
332 /// else in an agent connection to say.
333 ///
334 /// -1 rather than optional so the accept loop's guard is the same
335 /// comparison every other fd in this file uses.
336 agent_listener: std.posix.fd_t = -1, 283 agent_listener: std.posix.fd_t = -1,
337 /// The path of that socket — the value the shell was handed. Owned; 284 /// The path of that socket — the value the shell was handed. Owned;
338 /// freed with the listener in both teardown paths (Session.closeAgent). 285 /// freed with the listener in both teardown paths (Session.closeAgent).
@@ -345,52 +292,30 @@ pub const Session = struct {
345 /// The terminal modes as last put on the wire, or null before the first 292 /// The terminal modes as last put on the wire, or null before the first
346 /// sample. Same discipline as mode_sent: what clients have been TOLD. 293 /// sample. Same discipline as mode_sent: what clients have been TOLD.
347 term_modes_sent: ?proto.TermModes = null, 294 term_modes_sent: ?proto.TermModes = null,
348 /// The window title as last put on the wire, or null before the first 295 /// The window title as last put on the wire. OWNED: the engine rewrites
349 /// one. Same "what clients have been TOLD" discipline as the two fields 296 /// its one title buffer in place, so a borrowed slice would compare the
350 /// above, but OWNED: the engine keeps one title buffer and rewrites it 297 /// new title against itself.
351 /// in place on the next OSC 0, so a borrowed slice would compare the
352 /// new title against itself. Freed in both teardown paths.
353 title_sent: ?[]const u8 = null, 298 title_sent: ?[]const u8 = null,
354 /// The last side-channel event of each kind that a reconnecting client 299 /// The last side-channel event of each kind a reconnecting client might
355 /// might still be owed, with the seq it happened at. One slot per kind 300 /// still be owed, with the seq it happened at. One slot per EVENT kind,
356 /// rather than a log: two copies during a gap means the last one wins, 301 /// not per clipboard target: two copies during a gap means the last wins,
357 /// which is what the user meant, and forty bells means one ding. 302 /// and forty bells means one ding. The clipboard slot holds the USER'S
358 /// 303 /// TEXT, so it is dropped as soon as its seq stops being servable.
359 /// "Per kind" is per EVENT kind, not per clipboard target: a later `p`
360 /// set replaces an earlier `c` set, because the slot holds the last
361 /// clipboard event with its target inside it.
362 ///
363 /// The clipboard slot holds the USER'S COPIED TEXT in daemon memory, so
364 /// it is dropped as soon as its seq stops being servable — at which
365 /// point it could never have been delivered to anyone anyway. Freed in
366 /// both teardown paths, like title_sent.
367 pending_clipboard: ?PendingEvent = null, 304 pending_clipboard: ?PendingEvent = null,
368 pending_bell: ?PendingEvent = null, 305 pending_bell: ?PendingEvent = null,
369 /// The session's command state machine (OSC 133). Seq-stamped copies of 306 /// The session's command state machine (OSC 133). Seq-stamped copies of
370 /// its transitions are what cmd_state/await_reply/status_reply carry. 307 /// its transitions are what cmd_state/await_reply/status_reply carry.
371 cmd: cmdmod.Tracker = .{}, 308 cmd: cmdmod.Tracker = .{},
372 /// The last completed command, frozen as it stood the moment it 309 /// The last completed command, frozen as it returned, and the one owner
373 /// returned, and the one owner of the return watermark: `.seq` is 310 /// of the return watermark: "a return happened at or before this seq" and
374 /// tracker.seq as of that return, so "a return happened at or before 311 /// "here is what it was" are one fact, not two fields to keep in step.
375 /// this seq" and "here is what it was" are one fact rather than two 312 /// An await asks about a PAST event, and the live tracker cannot describe
376 /// fields that have to be kept in step. Null until a command returns, 313 /// one — a shell's `D;code` and the next prompt's `A` arrive in a single
377 /// which is what every reader spells as a watermark of 0. 314 /// write, so live state loses the verdict a fraction of a pump after it.
378 ///
379 /// It exists because an await answers a question about a PAST event, and
380 /// the live tracker stops being able to describe that event almost
381 /// immediately. A real integrated shell emits `D;code` and the next
382 /// prompt's `A` in a single precmd write, so both fold into the tracker
383 /// before any await is looked at: phase is already back to `at_prompt`,
384 /// and the following command's `C` then clears the exit code outright.
385 /// Answering from live state therefore lost the verdict a fraction of a
386 /// pump after earning it — fast commands rode to their timeout, slow
387 /// ones degraded to pgid and threw the exit code away.
388 last_return: ?proto.CmdState = null, 315 last_return: ?proto.CmdState = null,
389 /// milliTimestamp of the last byte the pty produced; the settle floor. 316 /// `milliTimestamp` of the last byte the pty produced; the settle floor.
390 /// 0 means the session has never said anything, which no amount of 317 /// 0 is a session that has never spoken, which no elapsed silence should
391 /// elapsed silence should be read as a command having finished. 318 /// be read as a finished command. Per-session: this shell's silence.
392 /// Per-session because the floor measures THIS shell's silence: another
393 /// session's output must not reset it.
394 last_pty_ms: i64 = 0, 319 last_pty_ms: i64 = 0,
395 /// The `monoMs` at which an accepted `end_req` stops being polite. Null 320 /// The `monoMs` at which an accepted `end_req` stops being polite. Null
396 /// except between that accept and the SIGKILL `reap` sends once it passes. 321 /// except between that accept and the SIGKILL `reap` sends once it passes.
@@ -484,13 +409,9 @@ pub const Server = struct {
484 alloc: std.mem.Allocator, 409 alloc: std.mem.Allocator,
485 sessions: SessionTable = .{}, 410 sessions: SessionTable = .{},
486 /// The one plan every session's shell is spawned from — this, not 411 /// The one plan every session's shell is spawned from — this, not
487 /// opts.shell, is what future sessions spawn with. Computed once in 412 /// `opts.shell`. Computed once and shared: `shellint.install` mints a
488 /// init and shared deliberately: every session gets identical 413 /// fresh directory per call, so a plan per session would orphan all but
489 /// integration and one shim directory serves them all. 414 /// the last from teardown. Every slice points into `shellint_arena`.
490 /// (shellint.install mints a fresh exclusively-created directory per
491 /// call, so deriving a plan per session would litter one directory per
492 /// shell and orphan all but the last from teardown.) Every slice points
493 /// into shellint_arena.
494 spawn_plan: SpawnPlan, 415 spawn_plan: SpawnPlan,
495 // Spawn inputs retained for the manifest: SpawnPlan is the computed 416 // Spawn inputs retained for the manifest: SpawnPlan is the computed
496 // result (argv/env after injection), not the inputs that produced it. 417 // result (argv/env after injection), not the inputs that produced it.
@@ -515,15 +436,10 @@ pub const Server = struct {
515 /// gives up on it. Overridden small in tests; 8 MiB is far more than a 436 /// gives up on it. Overridden small in tests; 8 MiB is far more than a
516 /// live session ever queues, so tripping it means the peer is gone. 437 /// live session ever queues, so tripping it means the peer is gone.
517 pending_cap: usize = 8 * 1024 * 1024, 438 pending_cap: usize = 8 * 1024 * 1024,
518 /// Connections that haven't attached (mux d dump, or a client waiting 439 /// Connections that have not attached (`mux d dump`, or a client waiting
519 /// to attach). May send debug_dump; attach promotes into a client slot. 440 /// to attach); an attach promotes one into a client slot. An fd and a
520 /// 441 /// buffer, never a Sink: QUIC serves clients, and an observer is a local
521 /// An fd and a buffer, never a Sink: QUIC serves clients, not 442 /// one-shot tool that reads one answer over the unix socket and exits.
522 /// observers. An observer is a local one-shot tool (`mux d dump`,
523 /// `mux d stats`) that connects over the unix socket, reads one answer
524 /// and exits — there is no remote story for it, so giving it a Sink
525 /// would be generality with no second case. If that ever changes,
526 /// this is the comment that was wrong.
527 observers: [max_observers]?Observer = @splat(null), 443 observers: [max_observers]?Observer = @splat(null),
528 observer_idle_ms: i64 = observer_idle_ms_default, 444 observer_idle_ms: i64 = observer_idle_ms_default,
529 /// `.none` is a first-class answer, not a failure: QUIC is opt-in per 445 /// `.none` is a first-class answer, not a failure: QUIC is opt-in per
@@ -547,11 +463,9 @@ pub const Server = struct {
547 /// for why it is handed a `*Server` rather than holding one. 463 /// for why it is handed a `*Server` rather than holding one.
548 agents: AgentRelay = .{}, 464 agents: AgentRelay = .{},
549 stats: upgrade.Counters = .{}, 465 stats: upgrade.Counters = .{},
550 // Set by the upgrade_req handler, checked by the run loop after each 466 // Set by the `upgrade_req` handler, checked by the run loop: the reply
551 // pump: the reply must drain before the exec, and the exec must happen 467 // must drain before the exec, and a mid-handler exec would strand the
552 // outside the frame handler (mid-handler exec would strand the observer 468 // observer fd and skip the close-all.
553 // fd and skip the close-all). Shaped like stop_req's shutdown_flag but
554 // per-instance: the upgrade path carries the candidate's path and memfd.
555 pending_upgrade: ?PendingUpgrade = null, 469 pending_upgrade: ?PendingUpgrade = null,
556 // The daemon's own version string, set at init from build_options. 470 // The daemon's own version string, set at init from build_options.
557 // Stored on the struct so the upgrade handler can reach it without 471 // Stored on the struct so the upgrade handler can reach it without
@@ -571,19 +485,12 @@ pub const Server = struct {
571 cols: u16 = 80, 485 cols: u16 = 80,
572 rows: u16 = 24, 486 rows: u16 = 24,
573 /// Inject the OSC 133 mark scripts into the session shell. OFF by 487 /// Inject the OSC 133 mark scripts into the session shell. OFF by
574 /// default: the shim is not free — under zsh it costs the user their 488 /// default: the shim costs the user their `~/.zshenv` under zsh and
575 /// `~/.zshenv`, under bash it displaces their DEBUG trap — and what 489 /// their DEBUG trap under bash, and only `mux a` reads what it buys.
576 /// it buys, a knowable exit code, is read only by `mux a`. A caller
577 /// that wants marks says so; `mux d start` says so for
578 /// `MUX_SHELL_INTEGRATION=1`. A shell shellint has no scripts for is
579 /// unaffected either way.
580 shell_integration: bool = false, 490 shell_integration: bool = false,
581 /// Extra variables for the session shell, set after the injection's 491 /// Extra variables for the session shell, set after the injection's
582 /// own so a caller can override one. The shell-integration tests 492 /// own so a caller can override one. The shell-integration tests point
583 /// point HOME at a temp directory with it: a shim that sources the 493 /// HOME at a temp dir with it, so no verdict depends on whose box ran.
584 /// box's real rc files is a test whose verdict depends on whose box
585 /// it ran on, and this feature's whole job is to be right about
586 /// shells it did not configure.
587 extra_env: []const Pty.EnvPair = &.{}, 494 extra_env: []const Pty.EnvPair = &.{},
588 // The daemon's own version, for the upgrade skew check. 495 // The daemon's own version, for the upgrade skew check.
589 version: []const u8 = "", 496 version: []const u8 = "",
@@ -651,14 +558,10 @@ pub const Server = struct {
651 parsed: *const upgrade.Parsed, 558 parsed: *const upgrade.Parsed,
652 version: []const u8, 559 version: []const u8,
653 ) !Server { 560 ) !Server {
654 // Same pid, same children, same descriptors. No `sockpath.claim` 561 // Same pid, same children, same descriptors. No `sockpath.claim`: the
655 // here, deliberately: the inherited listener fd 562 // inherited listener fd IS the claim, and claim's probe would find our
656 // IS the claim, and claim's probe would find our own socket 563 // own socket answering. `version` is THIS binary's, never the
657 // answering and refuse the daemon that is already serving it. 564 // manifest's — the skew check measures against what is running now.
658 //
659 // `version` is THIS binary's, never the manifest's — the writer's
660 // version is the rollback story, and the next upgrade_req's skew
661 // check has to measure against what is running now.
662 const d = parsed.daemon; 565 const d = parsed.daemon;
663 566
664 // Everything the Server keeps a slice of is copied out of the 567 // Everything the Server keeps a slice of is copied out of the
@@ -675,13 +578,10 @@ pub const Server = struct {
675 .value = if (src.value) |v| try a.dupeZ(u8, v) else null, 578 .value = if (src.value) |v| try a.dupeZ(u8, v) else null,
676 }; 579 };
677 580
678 // The shim directory crossed as a path because live shells hold it 581 // The shim directory crossed as a path: live shells hold it in
679 // in ZDOTDIR (or --init-file) and it is pid-named: this process 582 // ZDOTDIR and it is pid-named, so a fresh one would orphan the old.
680 // could never mint that name again, and a fresh one would leave the 583 // Rewritten with THIS binary's scripts under the name shells hold —
681 // old directory with no owner to delete it. `shellint.prepare` 584 // `shellint.prepare` creates exclusively, since adopting is an attack.
682 // creates its directory exclusively — adopting one is a symlink
683 // attack — so the path is taken back out and rewritten with THIS
684 // binary's scripts under the name the shells already name.
685 const injection: shellint.Injection = if (d.shellint_dir) |src| blk: { 585 const injection: shellint.Injection = if (d.shellint_dir) |src| blk: {
686 const dir = try a.dupe(u8, src); 586 const dir = try a.dupe(u8, src);
687 std.fs.cwd().deleteTree(dir) catch {}; 587 std.fs.cwd().deleteTree(dir) catch {};
@@ -784,12 +684,9 @@ pub const Server = struct {
784 .end_row = rec.cmd.end_row, 684 .end_row = rec.cmd.end_row,
785 .exit_code = rec.cmd.exit_code, 685 .exit_code = rec.cmd.exit_code,
786 }; 686 };
787 // The verdict crosses; its watermark cannot. `CmdState.seq` in 687 // The verdict crosses; its watermark cannot. The delta tracker is
788 // a status reply is the RETURN watermark an await compares 688 // rebuilt from zero here, so an old-space seq is a watermark from
789 // `since_seq` against, and the delta tracker is rebuilt from 689 // the future that no later return can exceed.
790 // zero here — a seq from the old space is a watermark from the
791 // future that no later return can exceed, and it swallowed the
792 // first await after every upgrade.
793 s.last_return = if (rec.last_return) |lr| blk: { 690 s.last_return = if (rec.last_return) |lr| blk: {
794 var restamped = lr; 691 var restamped = lr;
795 restamped.seq = s.tracker.seq; 692 restamped.seq = s.tracker.seq;
@@ -804,12 +701,9 @@ pub const Server = struct {
804 s.name_len = @intCast(n); 701 s.name_len = @intCast(n);
805 } 702 }
806 703
807 // The UDP socket crossed bound and the key crossed as bytes, so TLS 704 // The UDP socket crossed bound and the key as bytes, so TLS stands
808 // stands back up on the same port with the same PSK. Both arms 705 // back up on the same port with the same PSK. Both arms become
809 // become `.owned`: whatever held the reference in the previous image 706 // `.owned`: whatever held the previous reference went with the image.
810 // went with it, and deinit is the only thing left that could free
811 // this one. The handler's ctx is bound in `run`, which is the first
812 // place the Server has its final address.
813 switch (d.quic.arm) { 707 switch (d.quic.arm) {
814 .none => {}, 708 .none => {},
815 .borrowed, .owned => { 709 .borrowed, .owned => {
@@ -843,13 +737,9 @@ pub const Server = struct {
843 /// it is the one part of starting a daemon that is neither the engine, 737 /// it is the one part of starting a daemon that is neither the engine,
844 /// the pty nor the listener, and inlining it buried those three. 738 /// the pty nor the listener, and inlining it buried those three.
845 fn prepareSpawn(a: std.mem.Allocator, opts: Options) !SpawnPlan { 739 fn prepareSpawn(a: std.mem.Allocator, opts: Options) !SpawnPlan {
846 // Beside the socket: that directory is already private, already 740 // Beside the socket: that directory is already private, runtime-
847 // runtime-appropriate and already per-user, which is three 741 // appropriate and per-user, which is what the shims need. What it is
848 // properties the shims need and none of them are ours to re-derive. 742 // called and what to say on failure are `shellint.install`'s to report.
849 // What the directory under it is CALLED, whether one was created,
850 // and what to say when the attempt fails are all shellint's —
851 // `install` reports the directory it made, so nothing here has to
852 // re-derive from the shell what that call already knew.
853 const injection: shellint.Injection = if (opts.shell_integration) 743 const injection: shellint.Injection = if (opts.shell_integration)
854 shellint.install(a, std.fs.path.dirname(opts.sock_path) orelse ".", opts.shell) 744 shellint.install(a, std.fs.path.dirname(opts.sock_path) orelse ".", opts.shell)
855 else 745 else
@@ -863,13 +753,9 @@ pub const Server = struct {
863 opts: Options, 753 opts: Options,
864 injection: shellint.Injection, 754 injection: shellint.Injection,
865 ) !SpawnPlan { 755 ) !SpawnPlan {
866 // Split from `prepareSpawn` for adoption: a resumed daemon must NOT 756 // Split from `prepareSpawn` for adoption: a resumed daemon must not
867 // mint a second shim directory — `initFromManifest` re-prepares the 757 // mint a second shim directory. shellint and pty each speak their own
868 // one the live shells already name — and everything below this line 758 // `EnvPair` to stay leaves, so the mapping lives here.
869 // is identical either way.
870 // shellint speaks its own EnvPair so it can stay a leaf, and so can
871 // pty; the daemon is the one place that knows about both, so the
872 // mapping lives here.
873 const env = try a.alloc(Pty.EnvPair, injection.env.len + opts.extra_env.len + 1); 759 const env = try a.alloc(Pty.EnvPair, injection.env.len + opts.extra_env.len + 1);
874 for (injection.env, env[0..injection.env.len]) |src, *dst| { 760 for (injection.env, env[0..injection.env.len]) |src, *dst| {
875 dst.* = .{ .key = src.key, .value = src.value }; 761 dst.* = .{ .key = src.key, .value = src.value };
@@ -878,15 +764,10 @@ pub const Server = struct {
878 // the child sees (see the loop in Pty.spawnArgv). 764 // the child sees (see the loop in Pty.spawnArgv).
879 @memcpy(env[injection.env.len..][0..opts.extra_env.len], opts.extra_env); 765 @memcpy(env[injection.env.len..][0..opts.extra_env.len], opts.extra_env);
880 766
881 // After extra_env, not before: this is the daemon's identity, not an 767 // After `extra_env`, not before: this is the daemon's identity, and a
882 // option, and a client reads it back to refuse attaching to the very 768 // caller that could overwrite it would hand a shell a lie about where
883 // session it is running inside (`wallview.showsSelf`). A caller 769 // it is (`wallview.showsSelf` reads it back). One plan serves every
884 // that could overwrite it could hand a shell a lie about where it is. 770 // session, so only the socket belongs here; `createSession` adds the name.
885 // One plan serves every session, so only the socket belongs here —
886 // the session name differs per session and createSession appends it.
887 // Both names come from `protocol`, which is where the readers get
888 // them; a shell only ever sees what the planter wrote, so a rename
889 // on one side alone would fail silently.
890 env[env.len - 1] = .{ .key = proto.sock_env, .value = try a.dupeZ(u8, opts.sock_path) }; 771 env[env.len - 1] = .{ .key = proto.sock_env, .value = try a.dupeZ(u8, opts.sock_path) };
891 772
892 // argv is the shell plus whatever the injection adds, null-terminated 773 // argv is the shell plus whatever the injection adds, null-terminated
@@ -923,32 +804,18 @@ pub const Server = struct {
923 .borrowed => {}, 804 .borrowed => {},
924 .none => {}, 805 .none => {},
925 } 806 }
926 // Only unlink the socket path if it still refers to *our* socket — a 807 // Unlink only if the path still names *our* socket: a newer daemon
927 // newer daemon may have replaced the file since we bound it, and 808 // may have replaced the file, and deleting that one would steal its
928 // deleting that one would hand its clients the same field incident 809 // clients. The stat comes AFTER the close, because a successor only
929 // the socket-steal fix exists to prevent. 810 // claims once nothing is listening — that narrows the race to the
930 // The stat comes AFTER the close. Statting first was vestigial: 811 // stat→unlink gap, which is the floor Linux gives for deleting by name.
931 // 8a82225 fstat'd the live descriptor and so needed the fd open,
932 // 6090604 switched to statting the path and killed that constraint.
933 // A successor only claims the path after concluding nothing is
934 // listening here — claim()'s probe cannot refuse while our fd is
935 // open — so asking on this side of our close is the correct side of
936 // that event, and it shrinks the window in which one can claim the
937 // path between our answer and our unlink down to the stat→unlink
938 // gap. That gap is the floor for deleting by name: Linux has no
939 // inode-predicated unlink. The close itself stays unconditional:
940 // the fd is ours whoever owns the path, and only the unlink touches
941 // the shared namespace.
942 self.listener.deinit(); 812 self.listener.deinit();
943 if (self.path_id.stillAt(self.sock_path)) { 813 if (self.path_id.stillAt(self.sock_path)) {
944 std.fs.cwd().deleteFile(self.sock_path) catch {}; 814 std.fs.cwd().deleteFile(self.sock_path) catch {};
945 } 815 }
946 // Two passes over one deadline. Every child is asked to go before 816 // Two passes over one deadline: every child is asked to go before any
947 // any is waited on, so a table of shells that ignore TERM costs one 817 // is waited on, so a table of shells that ignore TERM costs one
948 // `term_grace_ms`, not one each — a supervisor's stop timeout does 818 // `term_grace_ms` and not one each.
949 // not grow with `max_sessions`, and a daemon SIGKILLed halfway
950 // through leaves its socket and every per-session agent socket for
951 // the next `mux d start` to reason about.
952 for (&self.sessions.table) |*slot| { 819 for (&self.sessions.table) |*slot| {
953 if (slot.* != null) slot.*.?.pty.requestExit(); 820 if (slot.* != null) slot.*.?.pty.requestExit();
954 } 821 }
@@ -983,11 +850,9 @@ pub const Server = struct {
983 return &self.sessions.table[si].?; 850 return &self.sessions.table[si].?;
984 } 851 }
985 852
986 /// One poll iteration. A session whose shell has exited is torn down 853 /// One poll iteration. A session whose shell exited is torn down first,
987 /// first — its clients told and dropped — and the pump carries on. It 854 /// its clients told and dropped, and the pump carries on: no session's
988 /// answers nothing: no session's death ends the daemon, not even the 855 /// death ends the daemon, so an emptied one keeps serving its socket.
989 /// last one's, so an emptied daemon keeps serving its socket and a
990 /// fresh attach is born into it.
991 pub fn pumpOnce(self: *Server, timeout_ms: i32) !void { 856 pub fn pumpOnce(self: *Server, timeout_ms: i32) !void {
992 self.sessions.reap(self); 857 self.sessions.reap(self);
993 858
@@ -1048,11 +913,9 @@ pub const Server = struct {
1048 // Unconditional: expiry work is due whether or not a packet 913 // Unconditional: expiry work is due whether or not a packet
1049 // arrived, and this is the only place it can happen. 914 // arrived, and this is the only place it can happen.
1050 q.tick(); 915 q.tick();
1051 // Acks that just arrived are what frees room in a connection's 916 // Acks are what free room in a connection's egress ring, and a
1052 // egress ring, and a QUIC client has no descriptor of its own to 917 // QUIC client has no descriptor of its own to go writable: without
1053 // go writable. Without this, a client whose ring filled would 918 // this a filled ring waits for the next frame by coincidence.
1054 // sit on its backlog until the next frame happened to be queued
1055 // for it — progress by coincidence rather than by design.
1056 self.flushQuicClients(); 919 self.flushQuicClients();
1057 } 920 }
1058 921
@@ -1077,26 +940,19 @@ pub const Server = struct {
1077 } 940 }
1078 } 941 }
1079 942
1080 // Deliberately outside the arm above rather than folded into it: a 943 // Outside the arm above: a program can call tcsetattr and print
1081 // program can call tcsetattr and print nothing whatsoever, which is 944 // nothing, which is what every password prompt looks like from here.
1082 // exactly what `stty -echo` and every password prompt look like from 945 // Checking only when the pty spoke would miss the quietest changes.
1083 // here. Checking only when the pty spoke would miss the quietest
1084 // mode changes — the ones a predicting client most needs to hear
1085 // about — until the next unrelated byte of output happened along.
1086 for (&self.sessions.table, 0..) |*slot, si| { 946 for (&self.sessions.table, 0..) |*slot, si| {
1087 if (slot.* != null) self.pollPtyMode(si); 947 if (slot.* != null) self.pollPtyMode(si);
1088 } 948 }
1089 949
1090 if (fds[listener_idx].revents & std.posix.POLL.IN != 0) self.acceptConn(); 950 if (fds[listener_idx].revents & std.posix.POLL.IN != 0) self.acceptConn();
1091 951
1092 // Re-check each slot: an earlier arm (or a failed broadcast) may 952 // Re-check each slot: an earlier arm may have dropped a client whose
1093 // have dropped a client whose fd is still sitting in `fds`. 953 // fd is still in `fds`. The null check suffices only because no slot
1094 // The null check alone is enough only because no slot can be 954 // can be REFILLED between poll and here — move the observer loop above
1095 // *refilled* between poll and here: acceptConn only fills observer 955 // this one and it stops being true.
1096 // slots, and promotion into a client slot happens in
1097 // serviceObserver, which runs after this loop. So a slot that is
1098 // still non-null still holds the very fd whose revents we polled.
1099 // Move the observer loop above this one and that stops being true.
1100 for (0..max_clients) |i| { 956 for (0..max_clients) |i| {
1101 if (self.clients[i] == null) continue; 957 if (self.clients[i] == null) continue;
1102 const revents = fds[client_base + i].revents; 958 const revents = fds[client_base + i].revents;
@@ -1122,17 +978,10 @@ pub const Server = struct {
1122 self.reapIdleObservers(monoMs()); 978 self.reapIdleObservers(monoMs());
1123 979
1124 // After the client arms, so a channel opened this pump is routed by 980 // After the client arms, so a channel opened this pump is routed by
1125 // the activity order as the frames just handled left it, and so a 981 // the activity order those frames left. Servicing BEFORE accepting
1126 // client dropped up there is not handed a channel on its way out. 982 // keeps `revents` and the table in step: accepting first could free
1127 // Each loop re-tests its slot, because the arms above drop clients 983 // and refill a slot in one pass, and the loop below would then read a
1128 // and dropClient closes that client's channels. 984 // descriptor on the strength of the previous occupant's poll.
1129 //
1130 // Servicing BEFORE accepting is what keeps `revents` and the table
1131 // in step. Accepting first could free a slot (a client's agent_close
1132 // this pump) and refill it in the same pass, and the loop below
1133 // would then read a descriptor on the strength of the previous
1134 // occupant's poll — a blocking read on a socket nobody has written
1135 // to yet.
1136 for (0..max_agent_chans) |s| { 985 for (0..max_agent_chans) |s| {
1137 if (self.agents.chans[s] == null) continue; 986 if (self.agents.chans[s] == null) continue;
1138 if (fds[agent_chan_base + s].revents != 0) self.agents.service(self, s); 987 if (fds[agent_chan_base + s].revents != 0) self.agents.service(self, s);
@@ -1145,28 +994,20 @@ pub const Server = struct {
1145 } 994 }
1146 } 995 }
1147 996
1148 // After every arm that can move the session on, so an await sees the 997 // After every arm that can move the session on, so an await sees THIS
1149 // marks, the pgid and the output silence that this pump produced 998 // pump's marks and silence — and before the QUIC drain, which is what
1150 // rather than last pump's — and before the QUIC drain below, because 999 // puts the frame it queues on the wire.
1151 // an await resolving here queues a frame and drainAll is what puts a
1152 // QUIC client's queued bytes on the wire. The other order would cost
1153 // every remote await a whole extra poll cycle.
1154 self.checkAwaits(); 1000 self.checkAwaits();
1155 1001
1156 // Last, and deliberately at the very end of the pump: the listener's 1002 // Last in the pump: the listener's send only QUEUES — draining inside
1157 // send only QUEUES now (draining from inside an ngtcp2 callback is 1003 // an ngtcp2 callback is the defect that avoids — so this is where a
1158 // the defect that change exists to remove), so this is where a QUIC 1004 // QUIC client's bytes actually leave, after all the frame handling.
1159 // client's bytes actually leave. It has to run after all the frame
1160 // handling above, or everything queued during this pump would wait
1161 // for the next one and every reply would cost a poll cycle.
1162 if (self.quicListener()) |q| q.drainAll(); 1005 if (self.quicListener()) |q| q.drainAll();
1163 } 1006 }
1164 1007
1165 /// Pumps until something asks the daemon to stop. The only nonzero exit 1008 /// Pumps until something asks the daemon to stop. Always 0: getting here
1166 /// `mux d start` has is a boot failure in main; getting here means the 1009 /// means the daemon served, and a supervisor reads a nonzero exit on a
1167 /// daemon served, so the answer is 0 whether the ask was `mux d stop` or 1010 /// clean shutdown as a crash. Only a boot failure in main exits nonzero.
1168 /// a SIGTERM from a supervisor — which reads a nonzero exit on a clean
1169 /// shutdown as a crash.
1170 pub fn run(self: *Server) !u8 { 1011 pub fn run(self: *Server) !u8 {
1171 // The pump cannot start on a listener whose handler points anywhere 1012 // The pump cannot start on a listener whose handler points anywhere
1172 // but here. An adopted listener (initFromManifest) is built before 1013 // but here. An adopted listener (initFromManifest) is built before
@@ -1190,11 +1031,9 @@ pub const Server = struct {
1190 1031
1191 fn acceptConn(self: *Server) void { 1032 fn acceptConn(self: *Server) void {
1192 const conn = self.listener.accept() catch return; 1033 const conn = self.listener.accept() catch return;
1193 // Nonblocking from the accept, and it stays that way through the 1034 // Nonblocking from the accept, and through the promotion into a
1194 // promotion into a client slot: every write on this fd is either a 1035 // client slot: a blocking fd lets one peer that stops reading stop the
1195 // bounded observer reply or flushClient's queue, and both are 1036 // daemon's only loop. Every write here tolerates a short write.
1196 // written for a short write. A blocking fd here lets one peer that
1197 // stops reading stop the daemon's only loop.
1198 setNonblocking(conn.stream.handle) catch { 1037 setNonblocking(conn.stream.handle) catch {
1199 conn.stream.close(); 1038 conn.stream.close();
1200 return; 1039 return;
@@ -1219,11 +1058,9 @@ pub const Server = struct {
1219 if (self.clients[i]) |*c| c.agent_offer = false; 1058 if (self.clients[i]) |*c| c.agent_offer = false;
1220 } 1059 }
1221 1060
1222 /// Latest wins, the doctrine the grid follows: the person typing is the 1061 /// Latest wins, as the grid does: the person typing is the person whose
1223 /// person whose agent signs. Decided once per connection — an in-flight 1062 /// agent signs. Decided once per connection — swapping identities under a
1224 /// channel stays pinned to the client it opened on, because swapping 1063 /// mid-exchange ssh fails the signature rather than moving it.
1225 /// identities under a mid-exchange ssh fails the signature rather than
1226 /// moving it.
1227 pub fn agentAnswerer(self: *const Server, si: usize) ?usize { 1064 pub fn agentAnswerer(self: *const Server, si: usize) ?usize {
1228 var best: ?usize = null; 1065 var best: ?usize = null;
1229 for (self.clients, 0..) |c, i| { 1066 for (self.clients, 0..) |c, i| {
@@ -1251,14 +1088,10 @@ pub const Server = struct {
1251 self.teardownClient(i, true); 1088 self.teardownClient(i, true);
1252 } 1089 }
1253 1090
1254 /// Queue one frame for the client in slot `i` and offer it to the kernel 1091 /// Queue one frame for the client in slot `i` and offer it to the kernel.
1255 /// straight away. Returns false if the client is gone — either it was 1092 /// False if the client is gone, so callers can skip the accounting. The
1256 /// already dropped, or this call dropped it — so callers can skip the 1093 /// only way anything reaches a client fd, and it never blocks: the daemon
1257 /// accounting for a send that will never happen. 1094 /// is single-threaded and one stalled peer would freeze everyone.
1258 ///
1259 /// This is the only way anything reaches a client fd. Nothing here ever
1260 /// blocks: that is the whole point, since the daemon is single-threaded
1261 /// and one stalled peer would otherwise freeze the session for everyone.
1262 pub fn queueFrame(self: *Server, i: usize, t: proto.MsgType, payload: []const u8) bool { 1095 pub fn queueFrame(self: *Server, i: usize, t: proto.MsgType, payload: []const u8) bool {
1263 if (self.clients[i] == null) return false; 1096 if (self.clients[i] == null) return false;
1264 const slot = &self.clients[i].?; 1097 const slot = &self.clients[i].?;
@@ -1301,14 +1134,9 @@ pub const Server = struct {
1301 _ = self.queueFrame(i, .selection_reply, payload.items); 1134 _ = self.queueFrame(i, .selection_reply, payload.items);
1302 } 1135 }
1303 1136
1304 /// Wait, up to `budget_ms` in total, for clients to accept what they are 1137 /// Wait up to `budget_ms` for clients to accept what they are owed. Only
1305 /// still owed. Only the one path after which a client gets no next pump 1138 /// the path where a client gets no next pump uses it — a session's death —
1306 /// uses this — a session's death, for the clients about to be dropped: 1139 /// because there the alternative to a bounded wait is losing the frame.
1307 /// mid-session the pump's own POLLOUT handling drains queues without
1308 /// anyone waiting, and that is the property the rest of this change
1309 /// exists to protect. Here there is no next pump, so the alternative to
1310 /// a bounded wait is losing the frame. A peer that never reads costs
1311 /// the daemon the budget and no more.
1312 pub fn drainPending(self: *Server, budget_ms: i64) void { 1140 pub fn drainPending(self: *Server, budget_ms: i64) void {
1313 const deadline = std.time.milliTimestamp() + budget_ms; 1141 const deadline = std.time.milliTimestamp() + budget_ms;
1314 var stalls: usize = 0; 1142 var stalls: usize = 0;
@@ -1324,13 +1152,10 @@ pub const Server = struct {
1324 for (&self.clients, 0..) |*slot, i| { 1152 for (&self.clients, 0..) |*slot, i| {
1325 if (slot.* == null) continue; 1153 if (slot.* == null) continue;
1326 if (slot.*.?.sink == .quic) { 1154 if (slot.*.?.sink == .quic) {
1327 // A QUIC client has no descriptor of its own to wait on, 1155 // A QUIC client has no descriptor to wait on, so its
1328 // so its egress is driven directly. It owes in two 1156 // egress is driven directly. It owes in TWO places: bytes
1329 // places, and only counting the first is how this arm 1157 // queued in the slot, and bytes the ring took but the peer
1330 // used to look like it worked: bytes still queued in the 1158 // has not acked. A send finishes when it is acked.
1331 // slot, AND bytes the ring took but the peer has not
1332 // acknowledged. A send is not finished when it is
1333 // accepted, it is finished when it is acked.
1334 self.flushClient(i); 1159 self.flushClient(i);
1335 if (self.clients[i]) |*c| { 1160 if (self.clients[i]) |*c| {
1336 const still = c.pending.items.len + c.sink.inFlight(); 1161 const still = c.pending.items.len + c.sink.inFlight();
@@ -1346,11 +1171,9 @@ pub const Server = struct {
1346 fds[n_sock] = .{ .fd = c.sink.pollFd(), .events = std.posix.POLL.OUT, .revents = 0 }; 1171 fds[n_sock] = .{ .fd = c.sink.pollFd(), .events = std.posix.POLL.OUT, .revents = 0 };
1347 n_sock += 1; 1172 n_sock += 1;
1348 } 1173 }
1349 // Give QUIC egress a chance to leave the box before deciding 1174 // Give QUIC egress a chance to leave before deciding there is
1350 // there is nothing left to wait for. drainAll as well as tick: 1175 // nothing to wait for. `drainAll` as well as `tick`: tick only
1351 // tick only services connections whose timer is due, and the 1176 // services connections whose timer is due.
1352 // exit status this path exists to deliver was merely queued by
1353 // the flushClient above.
1354 if (self.quicListener()) |q| { 1177 if (self.quicListener()) |q| {
1355 q.tick(); 1178 q.tick();
1356 q.drainAll(); 1179 q.drainAll();
@@ -1376,11 +1199,9 @@ pub const Server = struct {
1376 else 1199 else
1377 null; 1200 null;
1378 const ready = std.posix.poll(fds[0..n], drainWaitMs(remaining, hint)) catch return; 1201 const ready = std.posix.poll(fds[0..n], drainWaitMs(remaining, hint)) catch return;
1379 // A timeout is a verdict on this slice, not on the peer. The 1202 // A timeout is a verdict on this slice, not on the peer: the
1380 // deadline check at the top of the loop is what ends this; 1203 // deadline at the top of the loop ends this. Returning here gives
1381 // returning here instead gave up the moment ngtcp2's next timer 1204 // up exactly when a lossy path was about to make progress.
1382 // came due, which on a lossy path is exactly when the work was
1383 // about to happen.
1384 if (ready == 0) continue; 1205 if (ready == 0) continue;
1385 1206
1386 if (quic_idx) |qi| { 1207 if (quic_idx) |qi| {
@@ -1427,11 +1248,9 @@ pub const Server = struct {
1427 off += n; 1248 off += n;
1428 } 1249 }
1429 if (off == slot.pending.items.len) { 1250 if (off == slot.pending.items.len) {
1430 // Retaining the allocation is the right default — steady-state 1251 // Retaining the allocation is the right default, but a one-off
1431 // frames reuse it — but a one-off burst (a big snapshot to a 1252 // burst would keep its buffer for the session's life, up to
1432 // briefly slow client) would otherwise keep its buffer attached 1253 // `pending_cap` x `max_clients`. Oversized buffers go back.
1433 // to the slot for the rest of the session, up to pending_cap
1434 // times max_clients. Hand back anything oversized once it empties.
1435 if (slot.pending.capacity > 64 * 1024) { 1254 if (slot.pending.capacity > 64 * 1024) {
1436 slot.pending.clearAndFree(self.alloc); 1255 slot.pending.clearAndFree(self.alloc);
1437 } else { 1256 } else {
@@ -1456,22 +1275,16 @@ pub const Server = struct {
1456 } 1275 }
1457 1276
1458 // ---- QUIC glue ------------------------------------------------------- 1277 // ---- QUIC glue -------------------------------------------------------
1459 // 1278 // Three callbacks hold the entire difference between a QUIC client and a
1460 // Three callbacks, and between them they contain the entire difference 1279 // socket client. Everything past `pushInbound` has heard of neither.
1461 // between a QUIC client and a socket client. Everything past pushInbound
1462 // is code that has never heard of either.
1463 1280
1464 fn quicOnOpen(ctx: *anyopaque, id: u64) void { 1281 fn quicOnOpen(ctx: *anyopaque, id: u64) void {
1465 const self: *Server = @ptrCast(@alignCast(ctx)); 1282 const self: *Server = @ptrCast(@alignCast(ctx));
1466 const listener = self.quicListener() orelse return; 1283 const listener = self.quicListener() orelse return;
1467 const slot = self.freeClientSlot() orelse { 1284 const slot = self.freeClientSlot() orelse {
1468 // Session full: the same answer the socket path gives, sent the 1285 // Session full: the same answer the socket path gives. A short
1469 // same way, then the connection goes. 1286 // accept would truncate the refusal into a corrupt frame — and on
1470 // A short accept would be a truncated refusal, which reads as a 1287 // a just-opened connection that means something is very wrong.
1471 // corrupt frame rather than a "no". The ring is empty on a
1472 // connection that has just opened and this frame is a handful of
1473 // bytes, so a partial take here means something is very wrong;
1474 // either way, closing is the answer.
1475 _ = listener.send(id, &refusalFrame()) catch {}; 1288 _ = listener.send(id, &refusalFrame()) catch {};
1476 listener.closeConn(id); 1289 listener.closeConn(id);
1477 return; 1290 return;
@@ -1587,17 +1400,10 @@ pub const Server = struct {
1587 return 0; 1400 return 0;
1588 }, 1401 },
1589 }; 1402 };
1590 // The same refusals main.zig gives the --quic path, in the same 1403 // The same refusals main.zig gives `--quic`, in the same words:
1591 // words — literally the same, since `quic.keyRefusalBody` owns them 1404 // `quic.keyRefusalBody` owns them and this site owns only the prefix.
1592 // and this site owns only the prefix. An operator who has seen one 1405 // Every error routes there, catch-all included — nothing else in this
1593 // message should not have to learn a second phrasing for it, and 1406 // expression can fail, so an unclassified one is still an unread key.
1594 // this site's catch-all used to be exactly that second phrasing.
1595 //
1596 // Every error is routed, catch-all included, because every error
1597 // here can only have come from the load: there is nothing else in
1598 // this expression to have failed. So an unclassified one is still a
1599 // key that would not read, which is exactly what the body's fourth
1600 // sentence says.
1601 const key = quic.Key.load(key_path) catch |err| { 1407 const key = quic.Key.load(key_path) catch |err| {
1602 var buf: [quic.key_refusal_len]u8 = undefined; 1408 var buf: [quic.key_refusal_len]u8 = undefined;
1603 std.debug.print( 1409 std.debug.print(
@@ -1635,11 +1441,9 @@ pub const Server = struct {
1635 } 1441 }
1636 1442
1637 // ---- pty mode ----------------------------------------------------- 1443 // ---- pty mode -----------------------------------------------------
1638 //
1639 // The daemon holds the only fd that knows whether a keystroke will be 1444 // The daemon holds the only fd that knows whether a keystroke will be
1640 // echoed, and by whom. Shipping that is what turns a client's local echo 1445 // echoed. Shipping that turns a client's local echo from a guess into a
1641 // from a guess into a deduction; the client is told what the terminal 1446 // deduction: it is told what the terminal IS and decides for itself.
1642 // IS, and decides for itself what to do about it.
1643 1447
1644 fn readPtyMode(self: *Server, si: usize) ?proto.PtyModeFlags { 1448 fn readPtyMode(self: *Server, si: usize) ?proto.PtyModeFlags {
1645 // Not merely defensive: tcgetattr on a closed master is `unreachable` 1449 // Not merely defensive: tcgetattr on a closed master is `unreachable`
@@ -1649,11 +1453,9 @@ pub const Server = struct {
1649 return .{ .icanon = m.icanon, .echo = m.echo }; 1453 return .{ .icanon = m.icanon, .echo = m.echo };
1650 } 1454 }
1651 1455
1652 /// Read the pty's line discipline and, if it has moved since clients 1456 /// Read the pty's line discipline and, if it moved since clients were
1653 /// were last told, tell all of them. One tcgetattr per pump on an fd we 1457 /// last told, tell them. One tcgetattr per pump: polling is the only
1654 /// already own — no caching cleverness, because polling is the only 1458 /// mechanism there is, since nothing notifies us of another process.
1655 /// mechanism there is: nothing notifies a process that some other
1656 /// process changed the terminal.
1657 fn pollPtyMode(self: *Server, si: usize) void { 1459 fn pollPtyMode(self: *Server, si: usize) void {
1658 const flags = self.readPtyMode(si) orelse return; 1460 const flags = self.readPtyMode(si) orelse return;
1659 if (self.ses(si).mode_sent) |prev| { 1461 if (self.ses(si).mode_sent) |prev| {
@@ -1669,21 +1471,15 @@ pub const Server = struct {
1669 } 1471 }
1670 } 1472 }
1671 1473
1672 /// Tell one client what the pty is doing right now. A client that has 1474 /// Tell one client what the pty is doing right now. The broadcast above
1673 /// just attached has been told nothing, and the broadcast above only 1475 /// only fires on a CHANGE, so without this a client joining a session
1674 /// fires on a change — so without this, a client joining a session that 1476 /// sitting quietly at a prompt would wait forever to learn the mode.
1675 /// is sitting quietly at a prompt would wait for the mode to move before
1676 /// it learned anything about it, which is to say forever.
1677 fn sendPtyModeTo(self: *Server, si: usize, i: usize) void { 1477 fn sendPtyModeTo(self: *Server, si: usize, i: usize) void {
1678 const flags = self.ses(si).mode_sent orelse blk: { 1478 const flags = self.ses(si).mode_sent orelse blk: {
1679 // Reached whenever an attach lands before the mode has ever 1479 // Reached whenever an attach lands before the mode was ever
1680 // been polled — which is not only the pre-first-pump case a 1480 // polled: a QUIC handshake and attach completing in one pump get
1681 // socket client can hit. A QUIC client's bytes arrive from 1481 // here with `mode_sent` still null. Recorded as sent, so no
1682 // `q.readable()` at the top of the pump, and pollPtyMode runs 1482 // broadcast repeats the value to the client just handed it.
1683 // further down it, so a handshake and attach that complete in
1684 // the same pump reach here with mode_sent still null. Recorded
1685 // as sent, so this cannot be followed by a broadcast of the very
1686 // same value to the client that was just handed it.
1687 const f = self.readPtyMode(si) orelse return; 1483 const f = self.readPtyMode(si) orelse return;
1688 self.ses(si).mode_sent = f; 1484 self.ses(si).mode_sent = f;
1689 break :blk f; 1485 break :blk f;
@@ -1752,11 +1548,9 @@ pub const Server = struct {
1752 // deadline it was ever given. 1548 // deadline it was ever given.
1753 if (self.ses(si).end_by_ms == null) 1549 if (self.ses(si).end_by_ms == null)
1754 self.ses(si).end_by_ms = monoMs() + Pty.term_grace_ms; 1550 self.ses(si).end_by_ms = monoMs() + Pty.term_grace_ms;
1755 // An accepted end cancels a pending upgrade. `validateUpgrade` 1551 // An accepted end cancels a pending upgrade: `validateUpgrade`
1756 // refuses while any master is -1, but it decides a pump before 1552 // decides a pump before `run` execs, and an end accepted in between
1757 // `run` execs, and an end accepted in between would leave the exec 1553 // leaves the exec calling `clearCloexec(-1)` — a panic, not a refusal.
1758 // calling clearCloexec(-1) — `unreachable` in the pinned std, so a
1759 // daemon panic taking every session with it, not a refusal.
1760 self.cancelUpgrade(); 1554 self.cancelUpgrade();
1761 return .{ .accepted = true, .others = others, .reason = proto.end_reason.accepted }; 1555 return .{ .accepted = true, .others = others, .reason = proto.end_reason.accepted };
1762 } 1556 }
@@ -1783,11 +1577,9 @@ pub const Server = struct {
1783 self.observers[i] = null; 1577 self.observers[i] = null;
1784 } 1578 }
1785 1579
1786 /// One read per readiness, for both connection tables: `.bytes` is what 1580 /// One read per readiness, for both connection tables: `.bytes` arrived,
1787 /// arrived, `.again` a readiness with nothing behind it (the fds are 1581 /// `.again` is a readiness with nothing behind it, `.gone` is EOF or a
1788 /// nonblocking since acceptConn), `.gone` EOF or a dead socket. Stated 1582 /// dead socket. Stated once so client and observer cannot drift apart.
1789 /// once because the client and observer paths must not drift apart on
1790 /// the read discipline — they differ only in which table they drop from.
1791 const ReadOutcome = union(enum) { bytes: []u8, again, gone }; 1583 const ReadOutcome = union(enum) { bytes: []u8, again, gone };
1792 1584
1793 fn readConn(fd: std.posix.fd_t, buf: []u8) ReadOutcome { 1585 fn readConn(fd: std.posix.fd_t, buf: []u8) ReadOutcome {
@@ -1819,14 +1611,9 @@ pub const Server = struct {
1819 } 1611 }
1820 1612
1821 /// Feed bytes that arrived for client `i`, extracting whole frames as 1613 /// Feed bytes that arrived for client `i`, extracting whole frames as
1822 /// they complete. Every transport lands here — a socket read and a QUIC 1614 /// they complete. Every transport lands here, so from the frame onward
1823 /// stream chunk from the shared UDP socket alike — so that from the 1615 /// they take byte-identical paths. Only the byte-split test below asks
1824 /// frame onward they take byte-identical paths. 1616 /// for the adversarial chunk boundaries a live link rarely produces.
1825 ///
1826 /// The byte-split test below still earns its keep with a real caller in
1827 /// place: the chunk boundaries a live QUIC link happens to produce are
1828 /// not the adversarial ones — a header split down the middle, two
1829 /// frames in one write — and only the test asks for those.
1830 pub fn pushInbound(self: *Server, i: usize, bytes: []const u8) void { 1617 pub fn pushInbound(self: *Server, i: usize, bytes: []const u8) void {
1831 if (self.clients[i] == null) return; 1618 if (self.clients[i] == null) return;
1832 self.clients[i].?.inbound.appendSlice(self.alloc, bytes) catch { 1619 self.clients[i].?.inbound.appendSlice(self.alloc, bytes) catch {
@@ -1847,15 +1634,10 @@ pub const Server = struct {
1847 } 1634 }
1848 } 1635 }
1849 1636
1850 /// Everything a client can ask for, once its bytes are a frame. Reached 1637 /// Everything a client can ask for, once its bytes are a frame. Slot `i`
1851 /// from a socket read or from an injection; deliberately knows about 1638 /// is live on entry — `pushInbound` re-reads it before every dispatch,
1852 /// neither. 1639 /// because a handler can drop the client mid-loop — so the arms below
1853 /// 1640 /// index with `.?` rather than each re-testing it.
1854 /// Slot `i` is live on entry — pushInbound re-reads it before every
1855 /// dispatch, precisely because a handler can drop the client mid-loop —
1856 /// so the arms below index it with `.?` rather than each re-testing it.
1857 /// One arm used to carry its own null check; it read like the others
1858 /// were missing one.
1859 fn handleFrame(self: *Server, i: usize, frame: proto.Frame) void { 1641 fn handleFrame(self: *Server, i: usize, frame: proto.Frame) void {
1860 if (self.handleDaemonVerb(.{ .client = i }, frame)) return; 1642 if (self.handleDaemonVerb(.{ .client = i }, frame)) return;
1861 switch (frame.type) { 1643 switch (frame.type) {
@@ -1920,19 +1702,10 @@ pub const Server = struct {
1920 } 1702 }
1921 1703
1922 /// The verbs whose answer is the DAEMON rather than the connection: 1704 /// The verbs whose answer is the DAEMON rather than the connection:
1923 /// identical bytes for an attached client and for a one-shot `mux d` 1705 /// identical bytes for an attached client and a one-shot `mux d` tool.
1924 /// tool. Returns false for a verb this owner does not have — an attach, 1706 /// False for a verb that does depend on who asked. The two owners differ
1925 /// an upgrade, a status_req — which really does depend on who asked. 1707 /// only in how an answer LEAVES, which is all `replyTo` is: a client has a
1926 /// Written twice before, and the two copies were not the same program. 1708 /// send queue, an observer gets a bounded write and a truncation drops it.
1927 ///
1928 /// The tables differ only in how an answer LEAVES, which is all
1929 /// `replyTo` is: a client has a send queue and never blocks, while an
1930 /// observer has none — `mux d dump`, `mux d stats` and `mux d endpoint`
1931 /// read their one answer and exit. So the observer's write is bounded:
1932 /// a dump can be megabytes and must tolerate a short write, and a peer
1933 /// that stops reading costs this reply's budget, not the daemon's loop.
1934 /// Past the budget the frame is truncated, which is why a failed write
1935 /// drops the connection rather than leave half a frame on it.
1936 fn handleDaemonVerb(self: *Server, p: Peer, frame: proto.Frame) bool { 1709 fn handleDaemonVerb(self: *Server, p: Peer, frame: proto.Frame) bool {
1937 switch (frame.type) { 1710 switch (frame.type) {
1938 .stats_req => { 1711 .stats_req => {
@@ -1948,11 +1721,9 @@ pub const Server = struct {
1948 self.replyTo(p, .stats_reply, text); 1721 self.replyTo(p, .stats_reply, text);
1949 }, 1722 },
1950 .sessions_req => { 1723 .sessions_req => {
1951 // Daemon-global for the same reason .stats_req is: the answer 1724 // Daemon-global like `.stats_req`: the answer is the session
1952 // is the session TABLE, so which session the asker sits in 1725 // TABLE, so who asked cannot change it. An observation, not
1953 // (or whether it sits in one at all) cannot change it. 1726 // activity, so it claims no grid.
1954 // Answered on the asking connection only — it is an
1955 // observation, not activity, so it claims no grid.
1956 var buf: SessionsBuf = undefined; 1727 var buf: SessionsBuf = undefined;
1957 self.replyTo(p, .sessions_reply, self.sessions.text(&buf)); 1728 self.replyTo(p, .sessions_reply, self.sessions.text(&buf));
1958 }, 1729 },
@@ -1961,11 +1732,9 @@ pub const Server = struct {
1961 self.replyTo(p, .endpoint_reply, &payload); 1732 self.replyTo(p, .endpoint_reply, &payload);
1962 }, 1733 },
1963 .debug_dump => { 1734 .debug_dump => {
1964 // Answers whatever the payload's tail names — it is a read 1735 // A read against a NAME, not a question about this
1965 // against a name, not a question about this connection's own 1736 // connection's session: an attached client may peek at any
1966 // session, and an attached client is free to peek at another 1737 // live one. `buildDump` owns the resolution and the refusal.
1967 // live session. See buildDump for the resolution and the
1968 // in-words unknown-name reply.
1969 const dump = self.buildDump(frame.payload) catch { 1738 const dump = self.buildDump(frame.payload) catch {
1970 self.dropPeer(p); 1739 self.dropPeer(p);
1971 return true; 1740 return true;
@@ -1983,14 +1752,9 @@ pub const Server = struct {
1983 var buf: [proto.end_reply_max_len]u8 = undefined; 1752 var buf: [proto.end_reply_max_len]u8 = undefined;
1984 self.replyTo(p, .end_reply, proto.encodeEndReply(&buf, v.accepted, v.others, v.reason)); 1753 self.replyTo(p, .end_reply, proto.encodeEndReply(&buf, v.accepted, v.others, v.reason));
1985 }, 1754 },
1986 // Shut down via the signal path, not a second one: this is the 1755 // The flag SIGTERM sets, not a second shutdown path, so the poll
1987 // flag SIGTERM sets, so the poll loop, deinit's unlink and the 1756 // loop and both teardowns stay already-tested code. The ack is the
1988 // pty teardown are all already-tested code. No reply is sent — 1757 // socket dying, which is what `mux d stop` polls for.
1989 // the ack is the socket dying, which is what `mux d stop` polls
1990 // for. `mux d stop` never attaches, so in the product this is
1991 // always the observer arm; a client reaching it grants no
1992 // authority an attached client lacks — see the .input arm, which
1993 // already writes to the pty master.
1994 .stop_req => shutdown_flag.store(true, .release), 1758 .stop_req => shutdown_flag.store(true, .release),
1995 else => return false, 1759 else => return false,
1996 } 1760 }
@@ -1998,18 +1762,9 @@ pub const Server = struct {
1998 } 1762 }
1999 1763
2000 fn onAttach(self: *Server, i: usize, frame: proto.Frame) void { 1764 fn onAttach(self: *Server, i: usize, frame: proto.Frame) void {
2001 // This used to snapshot unconditionally, on the reasoning 1765 // Not an unconditional snapshot: a QUIC connection is promoted to a
2002 // that a client attaching twice over one connection is rare 1766 // client slot at handshake, so its FIRST attach arrives here. Snapshot
2003 // enough not to deserve a second resync path. That was true 1767 // -serving them all would deny every QUIC client a delta resume.
2004 // while every client began life as an observer and did its
2005 // FIRST attach from there — and it stopped being true the
2006 // moment QUIC clients existed, because a QUIC connection is
2007 // promoted to a client slot when its handshake completes, so
2008 // its first attach arrives HERE. Every QUIC attach was
2009 // therefore snapshot-served, and a reconnecting QUIC client
2010 // could never resume from a delta no matter what it held.
2011 // Found by the reconnect scenario in e2e, which asserts the
2012 // counter rather than the rendering.
2013 const req = proto.decodeAttach(frame.payload) catch return; 1768 const req = proto.decodeAttach(frame.payload) catch return;
2014 // Re-resolved on every attach, because an attach on an 1769 // Re-resolved on every attach, because an attach on an
2015 // established connection IS the reconnect path — the name 1770 // established connection IS the reconnect path — the name
@@ -2022,15 +1777,9 @@ pub const Server = struct {
2022 return; 1777 return;
2023 }; 1778 };
2024 if (self.clients[i]) |*c| { 1779 if (self.clients[i]) |*c| {
2025 // An await's since_seq is a watermark in the OLD 1780 // Seq series are per-session, so an await's `since_seq` from the
2026 // session's tracker, and seq series are per-session — 1781 // old one would answer instantly or never, arbitrarily. Only on a
2027 // carrying it across would compare one session's 1782 // real change: a reconnect re-resolving the same name keeps its await.
2028 // watermark against another's last_return and answer
2029 // instantly or never, arbitrarily. The re-attach is the
2030 // event that invalidates it, so it is dropped here.
2031 // Only on a real change: a reconnect re-resolving the
2032 // same name is the common path and must not lose an
2033 // await it is still entitled to.
2034 if (c.session != si) c.await_state = null; 1783 if (c.session != si) c.await_state = null;
2035 c.session = si; 1784 c.session = si;
2036 } 1785 }
@@ -2039,9 +1788,7 @@ pub const Server = struct {
2039 1788
2040 /// The seating both attach arms end on, past every refusal so `attaches` 1789 /// The seating both attach arms end on, past every refusal so `attaches`
2041 /// never grows for an attach that seated nobody. Size is recorded only 1790 /// never grows for an attach that seated nobody. Size is recorded only
2042 /// when the grid really went there (a refused one leaves the slot 0x0), 1791 /// when the grid really went there, and modes precede the state they describe.
2043 /// modes go ahead of the state they describe, and only a size change
2044 /// repaints anyone but the joiner.
2045 fn seatClient(self: *Server, i: usize, si: usize, req: proto.AttachReq) void { 1792 fn seatClient(self: *Server, i: usize, si: usize, req: proto.AttachReq) void {
2046 self.stats.attaches += 1; 1793 self.stats.attaches += 1;
2047 self.bumpActivity(i); 1794 self.bumpActivity(i);
@@ -2064,30 +1811,11 @@ pub const Server = struct {
2064 } 1811 }
2065 1812
2066 fn onInput(self: *Server, i: usize, frame: proto.Frame) void { 1813 fn onInput(self: *Server, i: usize, frame: proto.Frame) void {
2067 // Latest wins follows *activity*, which includes typing: 1814 // Latest wins follows ACTIVITY, and typing is activity: the console
2068 // the console you are typing at claims the grid, exactly as 1815 // you type at claims the grid. Scrollback, stats and detach are
2069 // attaching or resizing from it would. Costs one broadcast 1816 // deliberately not — paging history from a small terminal would yank
2070 // per switch between differently-sized clients — the first 1817 // the grid from whoever is working. Claimed before the bytes go out,
2071 // keystroke moves the grid, and every keystroke after that 1818 // so the shell reacts at the size the typist is watching.
2072 // finds the sizes already equal and does nothing.
2073 //
2074 // The other client-initiated frames are deliberately not
2075 // activity: .fetch_scrollback would let paging history from
2076 // a small terminal yank the grid away from the client that
2077 // is actually working, and .stats_req/.debug_dump/.detach
2078 // are not somebody using the terminal at all. This is the
2079 // same three-verb line bumpActivity records, so the grid and
2080 // the activity order can never disagree about who is here.
2081 //
2082 // Claimed before the bytes go out, so the shell reacts to
2083 // this keystroke at the size the typist is watching. The
2084 // input is forwarded even if the broadcast dropped this
2085 // client — the bytes are already ours, and dropClient on an
2086 // empty slot is a no-op.
2087 //
2088 // A session-less slot (promoted, never attached) has no
2089 // shell these bytes could honestly reach: dropped, like
2090 // every other session-scoped verb below.
2091 const si = self.clients[i].?.session orelse return; 1819 const si = self.clients[i].?.session orelse return;
2092 self.bumpActivity(i); 1820 self.bumpActivity(i);
2093 self.claimGrid(si, i); 1821 self.claimGrid(si, i);
@@ -2111,19 +1839,11 @@ pub const Server = struct {
2111 } 1839 }
2112 1840
2113 fn onSelectionReq(self: *Server, i: usize, frame: proto.Frame) void { 1841 fn onSelectionReq(self: *Server, i: usize, frame: proto.Frame) void {
2114 // Like scrollback, selection is a client-local observation 1842 // Selection is a client-local observation, not input: it claims no
2115 // of the authoritative grid. In particular it is not input: 1843 // size and repaints nobody. Decoded BEFORE the session lookup, because
2116 // it must not claim this client's advertised size or repaint 1844 // a malformed payload carries no id to correlate and only silence is
2117 // anybody else. 1845 // honest — while a well-formed one from a session-less slot deserves
2118 // 1846 // its answer rather than the client's whole timeout.
2119 // Decoded BEFORE the session is looked up, and that order is
2120 // the whole of this arm's totality. A malformed payload
2121 // carries no id to correlate, so silence is the only honest
2122 // answer and the ordered-replies test above documents it. A
2123 // WELL-FORMED request from a session-less slot (promoted by
2124 // a QUIC handshake, never attached) does carry one, and
2125 // dropping that on the floor costs the client its whole
2126 // timeout for an answer that was one line away.
2127 const req = proto.decodeSelectionReq(frame.payload) catch return; 1847 const req = proto.decodeSelectionReq(frame.payload) catch return;
2128 const si = self.clients[i].?.session orelse { 1848 const si = self.clients[i].?.session orelse {
2129 self.queueSelectionReply(i, req.id, selectionReplyStatus(null), 0, ""); 1849 self.queueSelectionReply(i, req.id, selectionReplyStatus(null), 0, "");
@@ -2161,36 +1881,21 @@ pub const Server = struct {
2161 1881
2162 fn onStatusReq(self: *Server, i: usize, frame: proto.Frame) void { 1882 fn onStatusReq(self: *Server, i: usize, frame: proto.Frame) void {
2163 const si = self.clients[i].?.session orelse { 1883 const si = self.clients[i].?.session orelse {
2164 // A session-less slot (promoted, never attached — the 1884 // A session-less slot has nothing to compare a tail against, so
2165 // shape a QUIC connection has between handshake and its 1885 // the tail IS the question — an observer's `status_req`, reached
2166 // first attach) has no session of its own to compare a 1886 // over QUIC. A one-shot ask against a name, so the resolved index
2167 // tail against, so the tail IS the question — same as 1887 // is used and never stored on the slot.
2168 // an observer's status_req (serviceObserver's arm),
2169 // which only a unix connection can ever reach. Mirrors
2170 // debug_dump above for the same reason: this is a
2171 // one-shot ask against a name, not an attach, so the
2172 // resolved index is used and never stored on the slot.
2173 const found = self.sessions.find(frame.payload) orelse { 1888 const found = self.sessions.find(frame.payload) orelse {
2174 const name = SessionTable.safeName(frame.payload); 1889 const name = SessionTable.safeName(frame.payload);
2175 // Same wording as the observer arm: one stderr line 1890 // Same wording as the observer arm: one stderr line
2176 // an operator can grep for, since status_reply is a 1891 // an operator can grep for, since status_reply is a
2177 // fixed binary layout with no room for words. 1892 // fixed binary layout with no room for words.
2178 std.debug.print("mux d: status_req for unknown session: {s}\n", .{name}); 1893 std.debug.print("mux d: status_req for unknown session: {s}\n", .{name});
2179 // The one word the wire has for no, the same one a 1894 // The one word the wire has for no. Before the drop, not
2180 // refused attach gets. Before the drop, not instead 1895 // instead of it: silence made `mux a status` report a dead
2181 // of it: silence made `mux a status --session nosuch` 1896 // daemon. It outlives the drop only inside an ngtcp2 callback,
2182 // report a dead daemon. 1897 // where the close defers to `.closing_quiet` and `reapClosing`
2183 // 1898 // puts the byte on the wire.
2184 // It outlives the drop only because this arm is
2185 // reached from inside an ngtcp2 callback: a QUIC
2186 // sink's send merely QUEUES (quic_server.send), and
2187 // the close that follows defers to `.closing_quiet`
2188 // rather than freeing, so `reapClosing`'s last drain
2189 // is what puts the byte on the wire. The same rope
2190 // `quicOnOpen`'s full-table refusal already hangs
2191 // from. Reach this arm from outside that callback
2192 // and the refusal is lost silently — the socket sink
2193 // is the one that flushes as it queues.
2194 _ = self.queueFrame(i, .exit_status, &.{1}); 1899 _ = self.queueFrame(i, .exit_status, &.{1});
2195 self.dropClient(i); 1900 self.dropClient(i);
2196 return; 1901 return;
@@ -2199,13 +1904,9 @@ pub const Server = struct {
2199 _ = self.queueFrame(i, .status_reply, &payload); 1904 _ = self.queueFrame(i, .status_reply, &payload);
2200 return; 1905 return;
2201 }; 1906 };
2202 // One rule, no aliasing: a non-empty tail must name the 1907 // One rule, no aliasing: a non-empty tail must name the slot's OWN
2203 // slot's OWN session or the frame is ignored outright — 1908 // session or the frame is ignored — never answered against the tail.
2204 // never answered against the tail instead. The client asked 1909 // The client asked two questions at once, and neither reading wins.
2205 // two different questions at once ("what is my slot
2206 // attached to" via the attach it already made, and "what is
2207 // session X" via this tail), and there is no reading where
2208 // both win; a mismatch gets silence, not a second opinion.
2209 if (frame.payload.len != 0 and !std.mem.eql(u8, frame.payload, self.ses(si).name())) return; 1910 if (frame.payload.len != 0 and !std.mem.eql(u8, frame.payload, self.ses(si).name())) return;
2210 const payload = proto.encodeStatusReply(self.buildStatusReply(si)); 1911 const payload = proto.encodeStatusReply(self.buildStatusReply(si));
2211 _ = self.queueFrame(i, .status_reply, &payload); 1912 _ = self.queueFrame(i, .status_reply, &payload);
@@ -2223,30 +1924,23 @@ pub const Server = struct {
2223 .timeout_ms = req.timeout_ms, 1924 .timeout_ms = req.timeout_ms,
2224 .started_ms = std.time.milliTimestamp(), 1925 .started_ms = std.time.milliTimestamp(),
2225 }; 1926 };
2226 // A return that already happened answers immediately — this 1927 // A return that already happened answers immediately, which is what
2227 // is what makes a reconnect re-issue safe. The request is 1928 // makes a reconnect re-issue safe — and keeps that true independent
2228 // answered inside this pump either way, since the pump-end 1929 // of the pump-end pass's ordering.
2229 // pass runs after frame handling; this call keeps that true
2230 // independent of pump-end ordering.
2231 self.checkAwaits(); 1930 self.checkAwaits();
2232 } 1931 }
2233 1932
2234 fn onAgentOffer(self: *Server, i: usize) void { 1933 fn onAgentOffer(self: *Server, i: usize) void {
2235 // Re-sent after every attach, so this is idempotent by 1934 // Idempotent by design: a redial re-offers on a slot that may already
2236 // design: a redial re-offers on a slot that may already be 1935 // be flagged, including one `sweepMuteAgentChans` took away — a
2237 // flagged, and nothing here is allowed to care. That 1936 // reattach may bring a working agent.
2238 // includes an offer sweepMuteAgentChans took away — a
2239 // reattach may bring a working agent, and a mute one costs
2240 // one more agent_answer_ms per attach, not per dial.
2241 self.clients[i].?.agent_offer = true; 1937 self.clients[i].?.agent_offer = true;
2242 } 1938 }
2243 1939
2244 fn onAgentData(self: *Server, i: usize, frame: proto.Frame) void { 1940 fn onAgentData(self: *Server, i: usize, frame: proto.Frame) void {
2245 const id = proto.decodeAgentId(frame.payload) catch return; 1941 const id = proto.decodeAgentId(frame.payload) catch return;
2246 // An id naming no channel of THIS client's is dropped in 1942 // An id naming no channel of THIS client's is dropped in silence: a
2247 // silence — the unknown-frame stance, applied to channels. 1943 // refusal that told "not yours" from "no such channel" apart would
2248 // Silence is also the only safe answer: a refusal that
2249 // distinguished "not yours" from "no such channel" would
2250 // tell a client what other clients hold. 1944 // tell a client what other clients hold.
2251 const s = self.agents.find(id, i) orelse return; 1945 const s = self.agents.find(id, i) orelse return;
2252 // The cap, enforced on the side that did not choose it. The 1946 // The cap, enforced on the side that did not choose it. The
@@ -2260,18 +1954,11 @@ pub const Server = struct {
2260 // A reply proves; a volley before any question does not, or 1954 // A reply proves; a volley before any question does not, or
2261 // a mute peer clears the clock by talking first. 1955 // a mute peer clears the clock by talking first.
2262 if (self.agents.chans[s].?.answer == .asked) self.agents.chans[s].?.answer = .proven; 1956 if (self.agents.chans[s].?.answer == .asked) self.agents.chans[s].?.answer = .proven;
2263 // The daemon's one deliberate blocking write, and it is not 1957 // The daemon's one deliberate blocking write. "Never block on a
2264 // the doctrine's exception it looks like. "Never block on a 1958 // client" holds because a client is a stranger across a link; this
2265 // client" holds because a client is a stranger across a 1959 // peer dialled a socket in `makeAgentDir`'s 0700 directory, so it is
2266 // link. This fd is not a client: it is the local agent 1960 // same-uid and could already signal this daemon. Agent traffic is
2267 // connection, whose peer dialled a socket in the 0700 1961 // small request-response, so the wedge to guard against does not exist.
2268 // directory `makeAgentDir` made, so it is same-uid — a peer
2269 // that could already stop this daemon with a signal, no
2270 // boundary crossed here that one does not cross. And agent
2271 // traffic is small request-response that cannot fill a
2272 // socket buffer, so the wedge this would have to guard
2273 // against does not exist. Turning this into a drop would
2274 // buy nothing and truncate a request mid-signature.
2275 proto.writeAllFd(self.agents.chans[s].?.fd, frame.payload[proto.agent_id_len..]) catch 1962 proto.writeAllFd(self.agents.chans[s].?.fd, frame.payload[proto.agent_id_len..]) catch
2276 self.agents.closeChan(self, s, .notify); 1963 self.agents.closeChan(self, s, .notify);
2277 } 1964 }
@@ -2283,24 +1970,15 @@ pub const Server = struct {
2283 if (self.agents.find(id, i)) |s| self.agents.closeChan(self, s, .silent); 1970 if (self.agents.find(id, i)) |s| self.agents.closeChan(self, s, .silent);
2284 } 1971 }
2285 1972
2286 /// Resolve any awaits that can be answered this pump. Granularity is 1973 /// Resolve any awaits that can be answered this pump, at the run loop's
2287 /// the run loop's 100ms tick — nothing here blocks, and no deadline 1974 /// 100ms granularity. Each client resolves against its OWN slot's
2288 /// folding into poll is needed at that resolution. One walk over the 1975 /// session: another session's return must never answer its await.
2289 /// clients, each resolved against its OWN slot's session: an await is
2290 /// a question about the shell its asker is attached to, and another
2291 /// session's return must never answer it.
2292 fn checkAwaits(self: *Server) void { 1976 fn checkAwaits(self: *Server) void {
2293 const now = std.time.milliTimestamp(); 1977 const now = std.time.milliTimestamp();
2294 // One ioctl per session somebody is awaiting on, not one per 1978 // One ioctl per SESSION being awaited on, not per waiting client: the
2295 // waiting client: the foreground process group is a property of a 1979 // foreground process group belongs to a session's pty, so every client
2296 // session's pty, so every awaiting client of the SAME session 1980 // of it reads the same number. Null covers both "nobody asked" and
2297 // reads the same number back — that is the boundary at which 1981 // "the ioctl failed". `saw_busy` stays per-client: the transition is its own.
2298 // "everyone gets the same answer" survived multi-session. Null
2299 // covers both "marks hold the floor, so nobody asked" — the probe
2300 // is skipped entirely then, exactly as before — and "the ioctl
2301 // failed", which has always been silently ignored. What stays
2302 // per-client is `saw_busy`: the transition each await is watching
2303 // for is its own.
2304 var wanted: [max_sessions]bool = @splat(false); 1982 var wanted: [max_sessions]bool = @splat(false);
2305 for (&self.clients) |*cslot| { 1983 for (&self.clients) |*cslot| {
2306 if (cslot.*) |*c| { 1984 if (cslot.*) |*c| {
@@ -2321,28 +1999,18 @@ pub const Server = struct {
2321 if (self.clients[i] == null) continue; 1999 if (self.clients[i] == null) continue;
2322 const slot = &self.clients[i].?; 2000 const slot = &self.clients[i].?;
2323 if (slot.await_state == null) continue; 2001 if (slot.await_state == null) continue;
2324 // The .await_req arm only accepts an await from a slot with a 2002 // Unreachable: `.await_req` only accepts from a slot with a
2325 // session, and the exit arm atop the pump drops a dying 2003 // session, and no client survives its session's exit. Kept because
2326 // session's clients before this runs — no client survives its 2004 // a slot that lost one has nothing an answer could be about.
2327 // session, so this stays unreachable. Kept because a slot that
2328 // lost one anyway has nothing an answer could honestly be about.
2329 const si = slot.session orelse continue; 2005 const si = slot.session orelse continue;
2330 const fg_pgid = fg_pgids[si]; 2006 const fg_pgid = fg_pgids[si];
2331 const a = &slot.await_state.?; 2007 const a = &slot.await_state.?;
2332 2008
2333 // 1. Marks: a return newer than since_seq answers with the full 2009 // 1. Marks: a return newer than `since_seq` answers with the full
2334 // story. Strictly greater: since_seq is "what I have". 2010 // story. Strictly greater — `since_seq` is "what I have".
2335 // 2011 // The WATERMARK alone decides, never live phase: a shell's
2336 // The watermark alone decides, and it is the snapshot's own 2012 // `D;code`+`A` burst lands in one pty read, so phase is back to
2337 // seq — the answer and the reason it qualifies are the same 2013 // `at_prompt` before this line runs.
2338 // record, so no reading of live tracker state can drift out
2339 // from under this test. Gating on
2340 // `cmd.phase == .returned` here was a defect: the shell's own
2341 // `D;code`+`A` burst lands in one pty read, so phase is back
2342 // to at_prompt before this line ever runs. Keying on the
2343 // watermark also answers correctly once the NEXT command is
2344 // already running — the client asked what had returned since
2345 // its seq, not what is happening now.
2346 if (self.ses(si).last_return) |st| { 2014 if (self.ses(si).last_return) |st| {
2347 if (st.seq > a.since_seq) { 2015 if (st.seq > a.since_seq) {
2348 self.answerAwait(i, st, .returned); 2016 self.answerAwait(i, st, .returned);
@@ -2384,14 +2052,10 @@ pub const Server = struct {
2384 // not a zero-length deadline but the absence of one — such an 2052 // not a zero-length deadline but the absence of one — such an
2385 // await ends only when marks, the pgid or settle end it. 2053 // await ends only when marks, the pgid or settle end it.
2386 if (a.timeout_ms > 0 and now - a.started_ms >= a.timeout_ms) { 2054 if (a.timeout_ms > 0 and now - a.started_ms >= a.timeout_ms) {
2387 // The phase is left as the session's own — a timeout reports 2055 // The phase stays the session's own — a timeout reports where
2388 // where things stand, mid-command and all — but the code goes, 2056 // things stand — but the code goes: nothing returned during
2389 // for the same reason it goes on the other two arms: nothing 2057 // THIS wait, so any code still standing is the previous
2390 // returned during THIS wait, so any code still standing is the 2058 // command's verdict, and `mechanism: "marks"` would sell it.
2391 // previous command's verdict. On a marks session it would have
2392 // ridden out under `mechanism: "marks"`, the one label the
2393 // published rule tells agents to trust, and been read as the
2394 // answer to a wait that has no answer.
2395 const st = self.fallbackState( 2059 const st = self.fallbackState(
2396 si, 2060 si,
2397 if (self.ses(si).cmd.marks_seen) .marks else .pgid, 2061 if (self.ses(si).cmd.marks_seen) .marks else .pgid,
@@ -2483,11 +2147,9 @@ pub const Server = struct {
2483 // Attach joins the session; it no longer displaces whoever 2147 // Attach joins the session; it no longer displaces whoever
2484 // was already there (takeover is retired; attach joins). 2148 // was already there (takeover is retired; attach joins).
2485 const slot = self.freeClientSlot() orelse { 2149 const slot = self.freeClientSlot() orelse {
2486 // Client table full. Refusing is the honest answer now 2150 // Client table full: the client exits nonzero rather than
2487 // that attaching can't evict anyone; the client exits 2151 // hanging on a silent socket. Checked BEFORE the name
2488 // nonzero rather than hanging on a silent socket. 2152 // resolves, so an unseatable client spawns no shell.
2489 // Checked BEFORE the name resolves so a client that
2490 // cannot be seated never spawns a shell either.
2491 return self.refuseObserver(i); 2153 return self.refuseObserver(i);
2492 }; 2154 };
2493 // Attach-or-create; null is the refusal (bad name, session 2155 // Attach-or-create; null is the refusal (bad name, session
@@ -2508,10 +2170,8 @@ pub const Server = struct {
2508 }, 2170 },
2509 .detach => self.dropObserver(i), 2171 .detach => self.dropObserver(i),
2510 // Where `mux d upgrade` lands: validate, reply, and let the run 2172 // Where `mux d upgrade` lands: validate, reply, and let the run
2511 // loop exec. The reply is blocking (observer has no send queue); 2173 // loop exec. Deferred via `pending_upgrade` so the close-all and
2512 // the exec is deferred to the run loop via pending_upgrade so 2174 // execve happen outside the observer's read cycle.
2513 // the frame handler returns cleanly and the close-all + execve
2514 // happen outside the observer's read cycle.
2515 .upgrade_req => { 2175 .upgrade_req => {
2516 const req = proto.parseUpgradeReq(frame.payload) catch return self.refuseUpgrade(i, "bad frame"); 2176 const req = proto.parseUpgradeReq(frame.payload) catch return self.refuseUpgrade(i, "bad frame");
2517 if (self.validateUpgrade(req, self.version)) |reason| { 2177 if (self.validateUpgrade(req, self.version)) |reason| {
@@ -2546,14 +2206,10 @@ pub const Server = struct {
2546 // tail against — the tail IS the question. 2206 // tail against — the tail IS the question.
2547 const si = self.sessions.find(frame.payload) orelse { 2207 const si = self.sessions.find(frame.payload) orelse {
2548 const name = SessionTable.safeName(frame.payload); 2208 const name = SessionTable.safeName(frame.payload);
2549 // status_reply is StatusReply, a fixed binary layout 2209 // `status_reply` is a fixed binary layout with no room for
2550 // with no room for words — unlike dump_reply, which is 2210 // words, so the answer is the `exit_status` 1 a refused
2551 // free-form bytes and can just say so. So the answer is 2211 // attach gets and the name goes on stderr. Bounded write:
2552 // the same exit_status 1 a refused attach gets, then the 2212 // the byte must be gone before the fd, not at the pump's price.
2553 // close, with the name on stderr where an operator (not
2554 // the wire) reads it. Bounded write like the attach
2555 // refusal above: the byte must be gone before the fd is,
2556 // but not at the price of the whole pump.
2557 std.debug.print("mux d: status_req for unknown session: {s}\n", .{name}); 2213 std.debug.print("mux d: status_req for unknown session: {s}\n", .{name});
2558 return self.refuseObserver(i); 2214 return self.refuseObserver(i);
2559 }; 2215 };
@@ -2565,26 +2221,18 @@ pub const Server = struct {
2565 } 2221 }
2566 2222
2567 /// `payload` is 1 byte (0 = plain, 1 = vt) ++ an optional session-name 2223 /// `payload` is 1 byte (0 = plain, 1 = vt) ++ an optional session-name
2568 /// tail, same pattern as attach/status_req/await_req — empty names the 2224 /// tail; empty names the default. Resolved via `findSession`, never
2569 /// default. Resolved via `findSession`, never `resolveSession`: a dump 2225 /// `resolveSession`: a read that could spawn a shell would make
2570 /// is a read, and a read that could spawn a shell would make `mux d dump 2226 /// `mux d dump --session typo` a way to stand one up. An unknown name
2571 /// --session typo` a way to accidentally stand one up. An unknown name 2227 /// answers IN WORDS, because `dump_reply` is the only frame this verb
2572 /// answers IN WORDS rather than as a dropped connection — the wire 2228 /// gets. The name quoted is the RESOLVED one. Caller owns the result.
2573 /// carries no separate channel for "no", and `dump_reply` is the only
2574 /// frame this verb ever gets, so the words have to travel inside it.
2575 /// The name in the message is the RESOLVED one (after ""→default), so
2576 /// it names what was actually looked up, not what was typed.
2577 /// Caller owns the result.
2578 fn buildDump(self: *Server, payload: []const u8) ![]const u8 { 2229 fn buildDump(self: *Server, payload: []const u8) ![]const u8 {
2579 const want_vt = payload.len >= 1 and payload[0] == 1; 2230 const want_vt = payload.len >= 1 and payload[0] == 1;
2580 const wire_name = if (payload.len >= 2) payload[1..] else ""; 2231 const wire_name = if (payload.len >= 2) payload[1..] else "";
2581 const si = self.sessions.find(wire_name) orelse { 2232 const si = self.sessions.find(wire_name) orelse {
2582 // Through safeName like the two status_req arms. This reply goes 2233 // Through `safeName` like the two `status_req` arms: `mux d dump`
2583 // back to the peer that asked, so it is self-inflicted rather 2234 // prints this straight to a console, and one rule for "a name in a
2584 // than an injection into someone else's terminal — but `mux d 2235 // message" beats remembering which site was the safe one.
2585 // dump` prints it straight to a console, and one rule for
2586 // "a name in a message" is cheaper than remembering which of
2587 // the three sites was the safe one.
2588 const name = SessionTable.safeName(wire_name); 2236 const name = SessionTable.safeName(wire_name);
2589 return std.fmt.allocPrint(self.alloc, "mux d: no such session: {s}\n", .{name}); 2237 return std.fmt.allocPrint(self.alloc, "mux d: no such session: {s}\n", .{name});
2590 }; 2238 };
@@ -2667,10 +2315,8 @@ pub const Server = struct {
2667 2315
2668 /// The only place a send is counted, so the four paths cannot drift on 2316 /// The only place a send is counted, so the four paths cannot drift on
2669 /// what a byte means. `to` is one client, null every client of THIS 2317 /// what a byte means. `to` is one client, null every client of THIS
2670 /// session — where a failed write drops only that client. Counted bytes 2318 /// session. Counted bytes exclude the 5-byte header here and in the
2671 /// are payload actually written, the 5-byte header excluded here and in 2319 /// counterfactual alike (~8% understated), as the bench measures it.
2672 /// the counterfactual (~8% understated). The counterfactual accrues once
2673 /// per EVENT and only if someone got it, as the bench measures it.
2674 fn countedSend(self: *Server, si: usize, t: proto.MsgType, payload: []const u8, to: ?usize) void { 2320 fn countedSend(self: *Server, si: usize, t: proto.MsgType, payload: []const u8, to: ?usize) void {
2675 var sent = false; 2321 var sent = false;
2676 for (0..max_clients) |i| { 2322 for (0..max_clients) |i| {
@@ -2698,19 +2344,14 @@ pub const Server = struct {
2698 } 2344 }
2699 2345
2700 /// Per-update path: diff and broadcast a delta; discontinuities resync. 2346 /// Per-update path: diff and broadcast a delta; discontinuities resync.
2701 /// The tracker advances even with nobody attached, so a client that 2347 /// The tracker advances with nobody attached, so a quiet detacher can
2702 /// detached quietly can still be caught up by delta when it returns — 2348 /// still be caught up — but WITHOUT diffing, since rendering every row
2703 /// but with nobody attached it advances WITHOUT diffing, because the 2349 /// to hash it would be thrown away. The returner gets a whole-grid delta.
2704 /// diff's cost is rendering every row and the result would be thrown
2705 /// away. The returning client is caught up by a delta covering the
2706 /// whole grid instead of one naming the rows that moved.
2707 pub fn sendUpdate(self: *Server, si: usize) void { 2350 pub fn sendUpdate(self: *Server, si: usize) void {
2708 const s = self.ses(si); 2351 const s = self.ses(si);
2709 // Asked before the diff, not after. update() renders every row to 2352 // Before the diff, not after: `update()` renders every row to hash
2710 // hash it, and with nobody attached that payload has nowhere to go 2353 // it, and with nobody attached that payload has nowhere to go — ~60%
2711 // — it was ~60% of the daemon's cycles on a full-width repaint. 2354 // of the daemon's cycles on a full-width repaint.
2712 // The check used to sit in the .advanced arm below, which is after
2713 // the whole loop has already run.
2714 if (!self.hasClientsIn(si)) { 2355 if (!self.hasClientsIn(si)) {
2715 // Still a discontinuity's caller: resyncSnapshot's rebuild is 2356 // Still a discontinuity's caller: resyncSnapshot's rebuild is
2716 // documented to happen with nobody attached, so the tracker is 2357 // documented to happen with nobody attached, so the tracker is
@@ -2740,14 +2381,9 @@ pub const Server = struct {
2740 } 2381 }
2741 2382
2742 /// Fold the engine's OSC 133 events into the command tracker and tell 2383 /// Fold the engine's OSC 133 events into the command tracker and tell
2743 /// attached clients about transitions. Runs after sendUpdate so 2384 /// clients about transitions. After `sendUpdate`, so `tracker.seq`
2744 /// tracker.seq already covers the same pty chunk — the off-by-one the 2385 /// already covers the same pty chunk. The pump's pty-read arm is the only
2745 /// spec pins (seq sampled post-feed). 2386 /// caller: marks from a test that feeds an engine directly sit pending.
2746 ///
2747 /// The pump's pty-read arm is the only caller, so marks produced by a
2748 /// test that feeds a session's engine directly (srv.ses(si).eng.feed)
2749 /// sit pending until a read arm runs — they are not lost, but they are
2750 /// not folded yet.
2751 fn drainMarkEvents(self: *Server, si: usize) void { 2387 fn drainMarkEvents(self: *Server, si: usize) void {
2752 const s = self.ses(si); 2388 const s = self.ses(si);
2753 for (s.eng.markEvents()) |ev| { 2389 for (s.eng.markEvents()) |ev| {
@@ -2778,57 +2414,24 @@ pub const Server = struct {
2778 s.eng.clearMarkEvents(); 2414 s.eng.clearMarkEvents();
2779 } 2415 }
2780 2416
2781 /// Ship the engine's side-channel events to this session's clients. 2417 /// Ship the engine's side-channel events to THIS session's clients, in
2782 /// Runs in the pty-read arm beside drainMarkEvents, for the same 2418 /// the pty-read arm beside `drainMarkEvents` because the events describe
2783 /// reason: the events describe the chunk that was just fed. 2419 /// the chunk just fed. A clipboard push crossing sessions would set the
2784 /// 2420 /// user's clipboard from a session they are not looking at.
2785 /// This session's clients alone. A clipboard write belongs to the shell
2786 /// that produced it, and a push crossing sessions would set the user's
2787 /// clipboard from a session they are not looking at.
2788 fn drainSideEvents(self: *Server, si: usize) void { 2421 fn drainSideEvents(self: *Server, si: usize) void {
2789 const s = self.ses(si); 2422 const s = self.ses(si);
2790 // This used to bail here when the session had no clients, on the 2423 // No bail when the session has no clients: with nobody attached is
2791 // reasoning that encoding for nobody is pure waste. It was right 2424 // when a recorded event matters MOST, since that is the gap a
2792 // while the events were live-only, and the pending slots are exactly 2425 // reconnecting client asks to be caught up across. Recording needs the
2793 // what makes it wrong: with nobody attached is when a recorded event 2426 // encode — the slot holds the wire payload. Bounded: one payload per
2794 // MATTERS MOST, because that is the gap a reconnecting client will 2427 // kind survives, and one buffer serves the whole drain.
2795 // ask to be caught up across. Encoding is what recording needs — the
2796 // slot holds the wire payload — so "skip the work when nobody is
2797 // listening" and "remember it for whoever comes back" cannot both be
2798 // had, and the feature is the one worth paying for.
2799 //
2800 // What the removal costs is bounded, which is why it is affordable:
2801 // the per-event encode is transient and reuses the one buffer below,
2802 // and what survives the call is at most one payload per kind, freed
2803 // as its replacement lands. A detached session ringing its bell in a
2804 // loop churns an allocation per ring where it used to churn none —
2805 // and holds one bell payload, not a log of them.
2806 //
2807 // One buffer for the whole drain rather than one per event: a burst
2808 // of bells is a single allocation that the first event sizes.
2809 var payload: std.ArrayList(u8) = .empty; 2428 var payload: std.ArrayList(u8) = .empty;
2810 defer payload.deinit(self.alloc); 2429 defer payload.deinit(self.alloc);
2811 // At most one bell per drain. N rings inside one 64 KiB chunk are one 2430 // At most one bell per DRAIN: N rings inside one 64 KiB chunk are one
2812 // ring to a human, and the bell is the cheapest event a session can 2431 // ring to a human, and `cat` on a binary produces ~256 of them per
2813 // emit in bulk: 0x07 is ~1/256 of random bytes, so `cat` on a binary 2432 // chunk. Per drain and not per session — a bell a second later is a
2814 // produces ~256 frames per chunk — 6 bytes on the wire and a 2433 // separate ring. The pending slot is unaffected: it is one slot per
2815 // writeAllFd on every client's stdout apiece, for a sound that cannot 2434 // kind at a `tracker.seq` that does not move inside this loop.
2816 // ring 256 times. sampleTermModes states the discipline this restores:
2817 // "bytes proportional to what changed". Modes dedup, titles are
2818 // sampled, the grid folds into one bounded delta; the bell was the
2819 // only client-bound stream left that did not.
2820 //
2821 // Per DRAIN, not per session. A bell a second later is a separate ring
2822 // and gets its own frame — only a burst the user could not have heard
2823 // apart folds. A session-lifetime flag would silence every bell after
2824 // the first, which is a different feature and a broken one.
2825 //
2826 // The pending slot is unaffected, which is worth stating because it is
2827 // the half that could have gone wrong: it is one slot per kind stamped
2828 // with tracker.seq, and tracker.seq does not move inside this loop. So
2829 // 256 rings recorded the old way left exactly what one ring leaves —
2830 // the same payload at the same seq, after 255 pointless dupe-and-free
2831 // pairs. Coalescing removes the churn and changes no recorded state.
2832 var rang = false; 2435 var rang = false;
2833 for (s.eng.sideEvents()) |ev| { 2436 for (s.eng.sideEvents()) |ev| {
2834 if (ev.kind == .bell) { 2437 if (ev.kind == .bell) {
@@ -2837,16 +2440,10 @@ pub const Server = struct {
2837 } 2440 }
2838 payload.clearRetainingCapacity(); 2441 payload.clearRetainingCapacity();
2839 switch (ev.kind) { 2442 switch (ev.kind) {
2840 // What a failed encode gives up is this one event: a 2443 // A failed encode gives up this one event, silently: there is
2841 // clipboard copy the user asked for that silently does not 2444 // nowhere in this file to say it. `continue` and not `return`,
2842 // happen. Silently because there is nowhere to say it — this 2445 // because a 64 KiB clipboard can exhaust memory while the
2843 // file makes no log calls and `upgrade.Counters` counts no 2446 // 1-byte bell behind it would have gone out fine.
2844 // errors, and a mechanism invented for this one path would
2845 // be the only one of its kind. `continue` rather than
2846 // `return` because the failure is per-event and the sizes
2847 // are wildly uneven: a 64 KiB clipboard can exhaust memory
2848 // while the 1-byte bell queued behind it would have gone
2849 // out fine.
2850 .clipboard => proto.encodeClipboardEvent( 2447 .clipboard => proto.encodeClipboardEvent(
2851 &payload, 2448 &payload,
2852 self.alloc, 2449 self.alloc,
@@ -2858,12 +2455,9 @@ pub const Server = struct {
2858 for (0..max_clients) |i| { 2455 for (0..max_clients) |i| {
2859 if (self.inSession(i, si)) _ = self.queueFrame(i, .term_event, payload.items); 2456 if (self.inSession(i, si)) _ = self.queueFrame(i, .term_event, payload.items);
2860 } 2457 }
2861 // After the live clients, and unconditionally: a session can have 2458 // After the live clients and unconditionally: one client watching
2862 // one client watching while another is away mid-reconnect, so 2459 // is no reason to leave a reconnecting one's gap empty. Stamped at
2863 // "somebody got it live" is no reason to leave the gap empty. 2460 // the `tracker.seq` `sendUpdate` already advanced over this chunk.
2864 // The stamp is tracker.seq, which sendUpdate has already advanced
2865 // over this same pty chunk — the off-by-one drainMarkEvents
2866 // documents, relied on here for the same reason.
2867 s.recordPending(self.alloc, ev.kind, payload.items); 2461 s.recordPending(self.alloc, ev.kind, payload.items);
2868 } 2462 }
2869 // Unconditional, and outside the loop: an encode that failed on OOM 2463 // Unconditional, and outside the loop: an encode that failed on OOM
@@ -2891,11 +2485,9 @@ pub const Server = struct {
2891 } 2485 }
2892 2486
2893 /// No history is kept: a reattaching client needs the current value, which 2487 /// No history is kept: a reattaching client needs the current value, which
2894 /// is also why sendResync sends it unconditionally. 2488 /// is why `sendResync` sends it unconditionally. The early return is the
2895 /// 2489 /// point — modes change twice in a session's life, chunks arrive by the
2896 /// The early return is the whole point. Modes change perhaps twice in a 2490 /// thousand, and a frame per chunk is not proportional to what changed.
2897 /// session's life while chunks arrive by the thousand, and a frame per
2898 /// chunk would break the "bytes proportional to what changed" discipline.
2899 fn sampleTermModes(self: *Server, si: usize) void { 2491 fn sampleTermModes(self: *Server, si: usize) void {
2900 const s = self.ses(si); 2492 const s = self.ses(si);
2901 const now = sampledModes(s.eng); 2493 const now = sampledModes(s.eng);
@@ -2909,14 +2501,10 @@ pub const Server = struct {
2909 } 2501 }
2910 } 2502 }
2911 2503
2912 /// An empty title is NOT sent, and that is the whole of mux's policy on 2504 /// An empty title is NOT sent: clearing would wipe whatever the user's own
2913 /// clearing. `ESC]0;BEL` on the client would wipe whatever the user's own 2505 /// terminal had in its title bar, and silence is not "set it to empty".
2914 /// terminal had in its title bar, and a session that never set a title has 2506 /// Accepted consequence — a session that genuinely clears its title leaves
2915 /// said nothing that entitles mux to do that — silence is not "set it to 2507 /// the last one standing. `sendResync` must apply the same two rules.
2916 /// empty". The consequence accepted: a session that sets a title and then
2917 /// genuinely clears it leaves the last one standing. `sendResync` applies
2918 /// the same two rules; they must agree, or an attach would assert
2919 /// something the sampler would never have sent.
2920 fn sampleTermTitle(self: *Server, si: usize) void { 2508 fn sampleTermTitle(self: *Server, si: usize) void {
2921 const s = self.ses(si); 2509 const s = self.ses(si);
2922 const now = s.eng.title(); 2510 const now = s.eng.title();
@@ -2986,11 +2574,9 @@ pub const Server = struct {
2986 .history_rows = s.eng.historyRows(), 2574 .history_rows = s.eng.historyRows(),
2987 .alt_screen = s.eng.onAltScreen(), 2575 .alt_screen = s.eng.onAltScreen(),
2988 // Live read, then the last one that worked, then a default. The 2576 // Live read, then the last one that worked, then a default. The
2989 // middle term is the one worth having: mode_sent is refreshed 2577 // middle term is the point: `mode_sent` is a real tcgetattr from
2990 // from a real tcgetattr every pump, so if this read is the one 2578 // last pump, so a failed read reports a remembered truth rather
2991 // that fails there is a remembered truth to report instead of a 2579 // than a fabricated "canonical and echoing".
2992 // fabricated "canonical and echoing" that may be the opposite of
2993 // what the session is doing.
2994 .mode = self.readPtyMode(si) orelse 2580 .mode = self.readPtyMode(si) orelse
2995 (s.mode_sent orelse .{ .icanon = true, .echo = true }), 2581 (s.mode_sent orelse .{ .icanon = true, .echo = true }),
2996 .cmd = self.cmdState(si, if (s.cmd.marks_seen) .marks else .pgid), 2582 .cmd = self.cmdState(si, if (s.cmd.marks_seen) .marks else .pgid),
@@ -3051,27 +2637,17 @@ pub const Server = struct {
3051 self.countedSend(si, .snapshot, payload, i); 2637 self.countedSend(si, .snapshot, payload, i);
3052 } 2638 }
3053 2639
3054 /// Attach/reattach for the client in slot `i`. Three cases: 2640 /// Attach/reattach for the client in slot `i`. Three cases: the size
3055 /// - the size changed: latest wins, so the new grid is broadcast to 2641 /// changed, so latest wins and everyone gets a snapshot; the size held
3056 /// everyone as a snapshot; 2642 /// and `have_seq` is serviceable, so this client alone gets a delta; or
3057 /// - same size and a serviceable have_seq: a delta, to this client 2643 /// it is unserviceable and this client alone gets a snapshot — the case a
3058 /// only, since only it is behind; 2644 /// FIRST attach takes, which is why it must not broadcast. Serviceable
3059 /// - same size and an unserviceable have_seq: a snapshot, again to 2645 /// means `have_epoch` names THIS daemon instance, or a client holding a
3060 /// this client only. This is the case a FIRST attach takes — the 2646 /// restarted daemon's seq would be told it is current.
3061 /// shipped client opens with have_seq=0 — which is exactly why it
3062 /// must not broadcast.
3063 ///
3064 /// Serviceable means the seq is ours to interpret: `have_epoch` must
3065 /// name THIS daemon instance. Without that check a client holding
3066 /// seq 900 from a daemon that has since been restarted would be told
3067 /// "you are current" against a session it has never seen a byte of.
3068 fn sendResync(self: *Server, si: usize, i: usize, have_seq: u64, have_epoch: u64, size_changed: bool) void { 2647 fn sendResync(self: *Server, si: usize, i: usize, have_seq: u64, have_epoch: u64, size_changed: bool) void {
3069 // After whichever content this call ends up sending. Modes and the 2648 // After whichever content this call sends. Modes and the title are
3070 // title are state, not history: a client returning to a session must 2649 // state, not history, so a snapshot needs them as much as a delta. One
3071 // be told what is true now, and one that got a full snapshot needs it 2650 // registration ahead of all three returns, so a fourth cannot skip it.
3072 // exactly as much as one that got a delta. One registration ahead of
3073 // all three returns below, so that a fourth cannot be added without
3074 // them.
3075 defer self.sendSampledStateTo(si, i); 2651 defer self.sendSampledStateTo(si, i);
3076 if (size_changed) { 2652 if (size_changed) {
3077 self.resyncSnapshot(si); 2653 self.resyncSnapshot(si);
@@ -3108,16 +2684,11 @@ pub const Server = struct {
3108 } 2684 }
3109 } 2685 }
3110 2686
3111 /// The delta branch of `sendResync` is the only caller: a client the grid 2687 /// The delta branch of `sendResync` is the only caller: a client that
3112 /// believes watched continuously is owed its gap, while one repainted from 2688 /// watched continuously is owed its gap, while one repainted from scratch
3113 /// scratch is a stranger — replaying a stale clipboard write would hijack 2689 /// is a stranger whose clipboard a replay would hijack. Wire order is
3114 /// its user's clipboard now. 2690 /// delta → events, since a terminal ACTS on a bell. `> have_seq`, not
3115 /// 2691 /// `>=`: an event at the client's own seq is one it saw.
3116 /// Wire order is delta → events: the host terminal ACTS on a bell or
3117 /// clipboard set, and acting before the repaint dings about a screen the
3118 /// user cannot see yet.
3119 ///
3120 /// `> have_seq`, not `>=`: an event at the client's own seq is one it saw.
3121 fn replayPending(self: *Server, si: usize, i: usize, have_seq: u64) void { 2692 fn replayPending(self: *Server, si: usize, i: usize, have_seq: u64) void {
3122 const s = self.ses(si); 2693 const s = self.ses(si);
3123 // Enum declaration order, so clipboard precedes bell. Fixed rather 2694 // Enum declaration order, so clipboard precedes bell. Fixed rather
@@ -3211,12 +2782,9 @@ pub const Server = struct {
3211 var recs: std.ArrayList(upgrade.SessionRec) = .empty; 2782 var recs: std.ArrayList(upgrade.SessionRec) = .empty;
3212 defer recs.deinit(a); 2783 defer recs.deinit(a);
3213 for (&self.sessions.table) |*slot| { 2784 for (&self.sessions.table) |*slot| {
3214 // BY POINTER, and it is load-bearing: `slot.* orelse continue` 2785 // BY POINTER: `slot.* orelse continue` copies the Session, and
3215 // copies the Session, and `name()` slices that copy's name_buf, 2786 // `name()` slices that copy's `name_buf`, which dies with the
3216 // which dies with the iteration. Every record then held a 2787 // iteration — every record would dangle into one reused stack slot.
3217 // dangling slice into one reused stack slot, so an upgrade
3218 // renamed every session after the first to the LAST one's name,
3219 // truncated to its own length — right lengths, wrong bytes.
3220 if (slot.* == null) continue; 2788 if (slot.* == null) continue;
3221 const s = &slot.*.?; 2789 const s = &slot.*.?;
3222 const vt = try s.eng.dumpState(a); 2790 const vt = try s.eng.dumpState(a);
@@ -3259,18 +2827,13 @@ pub const Server = struct {
3259 for (recs.items) |r| a.free(r.vt); 2827 for (recs.items) |r| a.free(r.vt);
3260 } 2828 }
3261 2829
3262 // Validation returns a refusal reason or null (accept). The checks run 2830 // A refusal reason, or null to accept. Ordered cheapest refusal first,
3263 // in order so a cheaper refusal comes before a dearer one; each reason 2831 // and each reason distinct so the requester can tell what failed.
3264 // is distinct so the requester can tell what failed. Checks 3–4
3265 // (child-run --version and --check) live in a helper so the e2e legs
3266 // drive them unchanged when chunk D lands --check.
3267 pub fn validateUpgrade(self: *Server, req: proto.UpgradeReq, my_version: []const u8) ?[]const u8 { 2832 pub fn validateUpgrade(self: *Server, req: proto.UpgradeReq, my_version: []const u8) ?[]const u8 {
3268 const a = self.alloc; 2833 const a = self.alloc;
3269 // 0. No session mid-hangup. `endSession` closed that master already, 2834 // 0. No session mid-hangup: `endSession` already closed that master,
3270 // and the exec's clearCloexec — like the new binary's sealFd on the 2835 // and the exec's `clearCloexec` is `unreachable` on -1, not an error.
3271 // pty_fd the manifest would carry — is `unreachable` on -1, not an 2836 // Before the child-run checks, which cost two spawns.
3272 // error. Before the child-run checks, which cost two spawns. The
3273 // wait is bounded by term_grace_ms, hence "retry".
3274 for (self.sessions.table) |slot| { 2837 for (self.sessions.table) |slot| {
3275 if (slot) |session| { 2838 if (slot) |session| {
3276 if (session.pty.master < 0) return a.dupe(u8, "session ending, retry") catch null; 2839 if (session.pty.master < 0) return a.dupe(u8, "session ending, retry") catch null;
@@ -3392,26 +2955,17 @@ pub const Server = struct {
3392 } 2955 }
3393 } 2956 }
3394 2957
3395 // The exec itself. NOTHING from Server.deinit runs on this path — no 2958 // The exec itself. NOTHING from `Server.deinit` runs here: the process
3396 // unlink, no deleteTree, no SIGTERM (`Server.deinit`). The 2959 // that owns the socket path, the shim dir and the agent dir never exits,
3397 // process that owns the socket path, the shim dir and the agent dir 2960 // it becomes the new binary. Every sink is bare-closed, NEVER sent
3398 // never exits; it becomes the new binary. 2961 // `exit_status` — that is a dying shell's word and makes clients exit
3399 // 2962 // instead of redial. A failed exec must leave a working daemon.
3400 // Bare-close every client sink and observer — NEVER exit_status: that
3401 // is a dying shell's word and makes clients exit instead of redial.
3402 // QUIC gets closeAll (loud goodbye) so WAN clients redial now, not
3403 // after the idle timeout.
3404 //
3405 // On exec FAILURE: log, restore FD_CLOEXEC on everything cleared, close
3406 // the memfd, keep serving — a failed exec must leave a working daemon.
3407 fn execUpgrade(self: *Server, path: []const u8, memfd: std.posix.fd_t) void { 2963 fn execUpgrade(self: *Server, path: []const u8, memfd: std.posix.fd_t) void {
3408 const a = self.alloc; 2964 const a = self.alloc;
3409 2965
3410 // Every return below is a FAILED exec — execveZ does not return on 2966 // Every return below is a FAILED exec, so the arming is spent either
3411 // success — so the arming is spent either way and is released here 2967 // way. A `pending_upgrade` left set would put the run loop into an
3412 // once, rather than at each failure return. A pending_upgrade left 2968 // exec on a loop.
3413 // set would put the run loop into an exec on a loop, and `path` is
3414 // this Server's copy of a frame payload that is long gone.
3415 defer { 2969 defer {
3416 a.free(path); 2970 a.free(path);
3417 std.posix.close(memfd); 2971 std.posix.close(memfd);
@@ -3501,12 +3055,9 @@ pub const Server = struct {
3501 3055
3502 pub const stats_session_fmt = " session {s} clients={d} seq={d}"; 3056 pub const stats_session_fmt = " session {s} clients={d} seq={d}";
3503 3057
3504 /// Widest text `fmt` can print: every `{d}` a 20-digit u64, every `{s}` 3058 /// Widest text `fmt` can print: every `{d}` a 20-digit u64, every `{s}` a
3505 /// a full-length name. Read off the format string, never counted by 3059 /// full-length name. Read off the format string, because a hand count does
3506 /// hand — a hand count does not move when a counter is added, and 3060 /// not move when a counter is added and too small drops the client.
3507 /// `statsText` writes into a fixed buffer, so too small is
3508 /// `error.WriteFailed`, which `.stats_req` answers by dropping the
3509 /// client.
3510 fn fmtWorstCase(comptime fmt: []const u8) usize { 3061 fn fmtWorstCase(comptime fmt: []const u8) usize {
3511 comptime { 3062 comptime {
3512 var n: usize = 0; 3063 var n: usize = 0;
@@ -3534,11 +3085,9 @@ pub const Server = struct {
3534 /// join uses, which is the slack that lets sessionsText be infallible. 3085 /// join uses, which is the slack that lets sessionsText be infallible.
3535 pub const sessions_text_len = max_sessions * (proto.session_name_max + 1); 3086 pub const sessions_text_len = max_sessions * (proto.session_name_max + 1);
3536 3087
3537 /// What a `sessions_reply` is rendered into, wherever it is rendered: 3088 /// What a `sessions_reply` is rendered into, wherever it is rendered. The
3538 /// the client arm has a send queue and the observer arm does not, so the 3089 /// client and observer arms cannot share a function, but they must not each
3539 /// two answers cannot share a function, but they must not each spell the 3090 /// spell the size: a caller that copied an array literal slips the assert.
3540 /// size. A third caller that copied an array literal would slip past the
3541 /// assert below; one that names this type cannot.
3542 pub const SessionsBuf = [sessions_text_len]u8; 3091 pub const SessionsBuf = [sessions_text_len]u8;
3543 comptime { 3092 comptime {
3544 // The wall reads a reply into a buffer of its own; a daemon that 3093 // The wall reads a reply into a buffer of its own; a daemon that
@@ -3557,17 +3106,10 @@ pub const Server = struct {
3557 } 3106 }
3558 3107
3559 /// Text, but machine-parsed: the bench harness and the e2e tests split on 3108 /// Text, but machine-parsed: the bench harness and the e2e tests split on
3560 /// these key=value pairs. Renaming or reordering fields breaks them. 3109 /// these key=value pairs, so renaming or reordering fields breaks them.
3561 /// 3110 /// Byte counters accrue when a frame is ACCEPTED INTO A CLIENT'S QUEUE,
3562 /// Byte counters are accrued when a frame is ACCEPTED INTO A CLIENT'S 3111 /// which `pending_cap` bounds. Daemon-global, with per-session segments in
3563 /// QUEUE, not when the kernel takes it — "sent" is a small lie, and 3112 /// slot order — and still one line, so no caller trips on a newline.
3564 /// pending_cap bounds it: no client can be more than one cap behind before
3565 /// it is dropped.
3566 ///
3567 /// Daemon-global: a leading `seq=` would be ONE session's tracker, which
3568 /// has no honest answer once there can be more than one, so it moved into
3569 /// a per-session segment in slot order. Still one line, so nothing here
3570 /// introduces a newline for a caller to trip on.
3571 pub fn statsText(self: *const Server, buf: []u8) ![]const u8 { 3113 pub fn statsText(self: *const Server, buf: []u8) ![]const u8 {
3572 var w: std.Io.Writer = .fixed(buf); 3114 var w: std.Io.Writer = .fixed(buf);
3573 try w.print( 3115 try w.print(
@@ -3604,10 +3146,8 @@ test {
3604 _ = @import("upgrade.zig"); 3146 _ = @import("upgrade.zig");
3605 3147
3606 // Reaching a file is what registers its tests; build.zig gates the list. 3148 // Reaching a file is what registers its tests; build.zig gates the list.
3607 // quic_server.zig is chained with these rather than with the three 3149 // quic_server.zig chains here rather than above: its suite binds real UDP
3608 // child files above: its suite binds real UDP sockets, so a wedge in it 3150 // sockets, so a wedge in it costs the same silence a daemon test's does.
3609 // costs the same silence a daemon test's does. The table ran it after
3610 // the daemon for that reason while it was still a row.
3611 _ = @import("server_test_agent.zig"); 3151 _ = @import("server_test_agent.zig");
3612 _ = @import("server_test_attach.zig"); 3152 _ = @import("server_test_attach.zig");
3613 _ = @import("server_test_await.zig"); 3153 _ = @import("server_test_await.zig");