aab60216
docs: what 32 sessions buys is names, not tiles on screen
a73x 2026-08-26 14:14
Commit message
docs/decisions.md
| Old | New | ||
|---|---|---|---|
| @@ -6691,11 +6691,16 @@ it. | |||
| 6691 | The 4 was "purpose bounds the surface" from the multi-session design: a number | 6691 | The 4 was "purpose bounds the surface" from the multi-session design: a number |
| 6692 | picked to keep the new thing small, never a measured limit. Multipane then grew | 6692 | picked to keep the new thing small, never a measured limit. Multipane then grew |
| 6693 | the wall to `wallview.max_tiles = 32` and the daemon cap did not move with it, | 6693 | the wall to `wallview.max_tiles = 32` and the daemon cap did not move with it, |
| 6694 | so a wall could ask for more sessions than the daemon would ever hold. It | 6694 | so a wall could name more sessions than the daemon would ever hold. It |
| 6695 | surfaced as a silently refused `Ctrl-\ c` — the client does say "cannot create a | 6695 | surfaced as a silently refused `Ctrl-\ c` — the client does say "cannot create a |
| 6696 | new session", but the relayout repaint that follows eats the notice, which is a | 6696 | new session", but the relayout repaint that follows eats the notice, which is a |
| 6697 | separate follow-up. | 6697 | separate follow-up. |
| 6698 | 6698 | ||
| 6699 | What the 32 buys is nameable sessions, not visible ones. Every tile is its own | ||
| 6700 | attach and therefore its own client slot, and `max_clients` is still 8, so a | ||
| 6701 | 32-tile wall against one daemon still refuses tiles nine and up. Cycling 32 | ||
| 6702 | sessions through eight tiles is the shape this enables. | ||
| 6703 | |||
| 6699 | Nothing about the 4 was performance. A session costs one engine, one pty and one | 6704 | Nothing about the 4 was performance. A session costs one engine, one pty and one |
| 6700 | shell, slots are filled lazily, and an empty slot polls fd -1. The two places | 6705 | shell, slots are filled lazily, and an empty slot polls fd -1. The two places |
| 6701 | the count does scale are worst cases that need a misbehaving peer to bite: the | 6706 | the count does scale are worst cases that need a misbehaving peer to bite: the |
| @@ -6703,10 +6708,11 @@ per-death drain is `max_sessions × 250ms` only when the whole table dies into | |||
| 6703 | stalled clients in one pass, and `pty.term_grace_ms` is paid only by a child | 6708 | stalled clients in one pass, and `pty.term_grace_ms` is paid only by a child |
| 6704 | that survives the master's close and then ignores SIGTERM. | 6709 | that survives the master's close and then ignores SIGTERM. |
| 6705 | 6710 | ||
| 6706 | `stats_text_len` is now derived from `max_sessions` rather than a literal with a | 6711 | `stats_text_len` is derived from `max_sessions` rather than a literal, so no |
| 6707 | comptime assert beside it, so the next bump cannot truncate a stats reply — the | 6712 | future bump to the table can truncate a stats reply. (Corrected 2026-08-26: the |
| 6708 | assert would have caught it, but only by failing the build, and deriving the | 6713 | per-line widths inside that derivation were still hand-counted and both |
| 6709 | number means there is nothing to catch. | 6714 | undercounted — 256 for a main line 375 wide, 44 for a session segment 63 wide. |
| 6715 | The bound is now read off the format strings themselves.) | ||
| 6710 | 6716 | ||
| 6711 | Filling a 32-slot table from a shell script found one thing worth writing down: | 6717 | Filling a 32-slot table from a shell script found one thing worth writing down: |
| 6712 | `acceptConn` parks every new connection in an OBSERVER slot and promotes it to a | 6718 | `acceptConn` parks every new connection in an OBSERVER slot and promotes it to a |
src/server.zig
| Old | New | ||
|---|---|---|---|
| @@ -255,8 +255,10 @@ const AwaitState = struct { | |||
| 255 | }; | 255 | }; |
| 256 | 256 | ||
| 257 | // Matches `wallview.max_tiles`: the 4 was surface-bounding (decisions.md, | 257 | // Matches `wallview.max_tiles`: the 4 was surface-bounding (decisions.md, |
| 258 | // 2026-08-25), never a measured limit, and a wall of 32 tiles could not | 258 | // 2026-08-25), never a measured limit. What it buys is 32 NAMEABLE |
| 259 | // fill it. | 259 | // sessions to cycle through, not 32 watched at once — every tile is its |
| 260 | // own attach and so its own client slot, and `max_clients` still refuses | ||
| 261 | // the ninth. | ||
| 260 | pub const max_sessions = 32; | 262 | pub const max_sessions = 32; |
| 261 | 263 | ||
| 262 | /// The smallest grid a session may exist at. Two owners used to decide | 264 | /// The smallest grid a session may exist at. Two owners used to decide |
| @@ -1079,8 +1081,8 @@ pub const Server = struct { | |||
| 1079 | // createSession: a session can therefore be BORN inside this loop, | 1081 | // createSession: a session can therefore be BORN inside this loop, |
| 1080 | // and if it lands in a slot the loop has already walked, an | 1082 | // and if it lands in a slot the loop has already walked, an |
| 1081 | // incremental counter never sees it. The daemon would then exit | 1083 | // incremental counter never sees it. The daemon would then exit |
| 1082 | // with a shell it forked moments earlier still running. The table | 1084 | // with a shell it forked moments earlier still running. Re-reading |
| 1083 | // is four entries; re-reading it costs nothing and cannot be wrong. | 1085 | // the table costs nothing and cannot be wrong. |
| 1084 | if (self.liveSessions() == 0) { | 1086 | if (self.liveSessions() == 0) { |
| 1085 | // Nested rather than one `and`: no live sessions and no code | 1087 | // Nested rather than one `and`: no live sessions and no code |
| 1086 | // means there were never any — unreachable after init, but this | 1088 | // means there were never any — unreachable after init, but this |