58a409f7
refactor: one read-only poll slot, pollIn
a73x 2026-08-29 10:01
Commit message
src/server/server.zig
| Old | New | ||
|---|---|---|---|
| @@ -141,6 +141,11 @@ pub fn countLive(slots: anytype) usize { | |||
| 141 | return n; | 141 | return n; |
| 142 | } | 142 | } |
| 143 | 143 | ||
| 144 | /// One read-only poll slot. A dead one polls -1, which poll(2) ignores. | ||
| 145 | fn pollIn(fd: std.posix.fd_t) std.posix.pollfd { | ||
| 146 | return .{ .fd = fd, .events = std.posix.POLL.IN, .revents = 0 }; | ||
| 147 | } | ||
| 148 | |||
| 144 | pub var shutdown_flag = std.atomic.Value(bool).init(false); | 149 | pub var shutdown_flag = std.atomic.Value(bool).init(false); |
| 145 | 150 | ||
| 146 | fn onShutdownSignal(_: c_int) callconv(.c) void { | 151 | fn onShutdownSignal(_: c_int) callconv(.c) void { |
| @@ -996,16 +1001,10 @@ pub const Server = struct { | |||
| 996 | max_sessions + 1 + max_clients + max_observers + | 1001 | max_sessions + 1 + max_clients + max_observers + |
| 997 | max_sessions + max_agent_chans + 1 | 1002 | max_sessions + max_agent_chans + 1 |
| 998 | ]std.posix.pollfd = undefined; | 1003 | ]std.posix.pollfd = undefined; |
| 999 | // A dead session slot polls fd -1, the same idiom as a dead client | ||
| 1000 | // slot below: poll(2) ignores negative fds. | ||
| 1001 | for (&self.sessions.table, 0..) |*slot, si| { | 1004 | for (&self.sessions.table, 0..) |*slot, si| { |
| 1002 | fds[si] = .{ | 1005 | fds[si] = pollIn(if (slot.*) |*s| s.pty.master else -1); |
| 1003 | .fd = if (slot.*) |*s| s.pty.master else -1, | ||
| 1004 | .events = std.posix.POLL.IN, | ||
| 1005 | .revents = 0, | ||
| 1006 | }; | ||
| 1007 | } | 1006 | } |
| 1008 | fds[listener_idx] = .{ .fd = self.listener.stream.handle, .events = std.posix.POLL.IN, .revents = 0 }; | 1007 | fds[listener_idx] = pollIn(self.listener.stream.handle); |
| 1009 | for (&self.clients, 0..) |*slot, i| { | 1008 | for (&self.clients, 0..) |*slot, i| { |
| 1010 | if (slot.*) |*c| { | 1009 | if (slot.*) |*c| { |
| 1011 | // POLLOUT only while something is owed: asking for it on an | 1010 | // POLLOUT only while something is owed: asking for it on an |
| @@ -1015,40 +1014,27 @@ pub const Server = struct { | |||
| 1015 | if (c.pending.items.len > 0) events |= std.posix.POLL.OUT; | 1014 | if (c.pending.items.len > 0) events |= std.posix.POLL.OUT; |
| 1016 | fds[client_base + i] = .{ .fd = c.sink.pollFd(), .events = events, .revents = 0 }; | 1015 | fds[client_base + i] = .{ .fd = c.sink.pollFd(), .events = events, .revents = 0 }; |
| 1017 | } else { | 1016 | } else { |
| 1018 | fds[client_base + i] = .{ .fd = -1, .events = std.posix.POLL.IN, .revents = 0 }; | 1017 | fds[client_base + i] = pollIn(-1); |
| 1019 | } | 1018 | } |
| 1020 | } | 1019 | } |
| 1021 | for (&self.observers, 0..) |*slot, i| { | 1020 | for (&self.observers, 0..) |*slot, i| { |
| 1022 | fds[obs_base + i] = .{ .fd = if (slot.*) |*o| o.fd else -1, .events = std.posix.POLL.IN, .revents = 0 }; | 1021 | fds[obs_base + i] = pollIn(if (slot.*) |*o| o.fd else -1); |
| 1023 | } | 1022 | } |
| 1024 | // Each session's agent socket and every live channel on it, on the | 1023 | // Each session's agent socket and every live channel on it. In the |
| 1025 | // same -1 idiom as the dead slots above. In the main poll rather | 1024 | // main poll rather than a loop of their own: an agent exchange is |
| 1026 | // than a loop of their own: an agent exchange is several round trips | 1025 | // several round trips deep inside an ssh handshake, and a second |
| 1027 | // deep inside an ssh handshake, and a second loop would pay it a | 1026 | // loop would pay it a poll cycle per leg. |
| 1028 | // poll cycle per leg. | ||
| 1029 | for (&self.sessions.table, 0..) |*slot, si| { | 1027 | for (&self.sessions.table, 0..) |*slot, si| { |
| 1030 | fds[agent_listener_base + si] = .{ | 1028 | fds[agent_listener_base + si] = pollIn(if (slot.*) |*s| s.agent_listener else -1); |
| 1031 | .fd = if (slot.*) |*s| s.agent_listener else -1, | ||
| 1032 | .events = std.posix.POLL.IN, | ||
| 1033 | .revents = 0, | ||
| 1034 | }; | ||
| 1035 | } | 1029 | } |
| 1036 | for (self.agents.chans, 0..) |slot, s| { | 1030 | for (self.agents.chans, 0..) |slot, s| { |
| 1037 | fds[agent_chan_base + s] = .{ | 1031 | fds[agent_chan_base + s] = pollIn(if (slot) |ch| ch.fd else -1); |
| 1038 | .fd = if (slot) |ch| ch.fd else -1, | ||
| 1039 | .events = std.posix.POLL.IN, | ||
| 1040 | .revents = 0, | ||
| 1041 | }; | ||
| 1042 | } | 1032 | } |
| 1043 | // One extra descriptor for every QUIC client there will ever be: | 1033 | // One extra descriptor for every QUIC client there will ever be: |
| 1044 | // they share it, which is the whole reason a client slot cannot be | 1034 | // they share it, which is the whole reason a client slot cannot be |
| 1045 | // a descriptor. | 1035 | // a descriptor. |
| 1046 | const quic_idx = fds.len - 1; | 1036 | const quic_idx = fds.len - 1; |
| 1047 | fds[quic_idx] = .{ | 1037 | fds[quic_idx] = pollIn(if (self.quicListener()) |q| q.pollFd() else -1); |
| 1048 | .fd = if (self.quicListener()) |q| q.pollFd() else -1, | ||
| 1049 | .events = std.posix.POLL.IN, | ||
| 1050 | .revents = 0, | ||
| 1051 | }; | ||
| 1052 | 1038 | ||
| 1053 | // ngtcp2's timers, folded in: the listener's earliest deadline | 1039 | // ngtcp2's timers, folded in: the listener's earliest deadline |
| 1054 | // shortens this poll, so retransmits and idle timeouts happen on | 1040 | // shortens this poll, so retransmits and idle timeouts happen on |
| @@ -2855,12 +2841,13 @@ pub const Server = struct { | |||
| 2855 | // What a failed encode gives up is this one event: a | 2841 | // What a failed encode gives up is this one event: a |
| 2856 | // clipboard copy the user asked for that silently does not | 2842 | // clipboard copy the user asked for that silently does not |
| 2857 | // happen. Silently because there is nowhere to say it — this | 2843 | // happen. Silently because there is nowhere to say it — this |
| 2858 | // file makes no log calls and Stats counts no errors, and a | 2844 | // file makes no log calls and `upgrade.Counters` counts no |
| 2859 | // mechanism invented for this one path would be the only one | 2845 | // errors, and a mechanism invented for this one path would |
| 2860 | // of its kind. `continue` rather than `return` because the | 2846 | // be the only one of its kind. `continue` rather than |
| 2861 | // failure is per-event and the sizes are wildly uneven: a | 2847 | // `return` because the failure is per-event and the sizes |
| 2862 | // 64 KiB clipboard can exhaust memory while the 1-byte bell | 2848 | // are wildly uneven: a 64 KiB clipboard can exhaust memory |
| 2863 | // queued behind it would have gone out fine. | 2849 | // while the 1-byte bell queued behind it would have gone |
| 2850 | // out fine. | ||
| 2864 | .clipboard => proto.encodeClipboardEvent( | 2851 | .clipboard => proto.encodeClipboardEvent( |
| 2865 | &payload, | 2852 | &payload, |
| 2866 | self.alloc, | 2853 | self.alloc, |
| @@ -3043,8 +3030,8 @@ pub const Server = struct { | |||
| 3043 | 3030 | ||
| 3044 | /// Every client of THIS session gets it, and the rebuild happens with | 3031 | /// Every client of THIS session gets it, and the rebuild happens with |
| 3045 | /// nobody attached too, so the tracker stays usable. No term_modes, | 3032 | /// nobody attached too, so the tracker stays usable. No term_modes, |
| 3046 | /// unlike sendResync: reaching here for a client that has never been | 3033 | /// unlike sendResync: every attached client was told the mode on attach |
| 3047 | /// through an attach breaks that, silently. | 3034 | /// and at each change; a resync that skips it breaks that, silently. |
| 3048 | pub fn resyncSnapshot(self: *Server, si: usize) void { | 3035 | pub fn resyncSnapshot(self: *Server, si: usize) void { |
| 3049 | if (!self.rebuildTracker(si)) return; | 3036 | if (!self.rebuildTracker(si)) return; |
| 3050 | if (!self.hasClientsIn(si)) return; | 3037 | if (!self.hasClientsIn(si)) return; |
| @@ -3591,6 +3578,8 @@ pub const Server = struct { | |||
| 3591 | self.stats.deltas, self.stats.delta_bytes, | 3578 | self.stats.deltas, self.stats.delta_bytes, |
| 3592 | self.stats.snapshot_equiv_bytes, self.liveClients(), | 3579 | self.stats.snapshot_equiv_bytes, self.liveClients(), |
| 3593 | self.stats.attaches, self.sessions.live(), | 3580 | self.stats.attaches, self.sessions.live(), |
| 3581 | // `agents.*`, not `stats.*`: the relay counts the refusals | ||
| 3582 | // live and `stats` only carries the adoption-time pair. | ||
| 3594 | self.agents.live(), self.agents.refused_no_offer, | 3583 | self.agents.live(), self.agents.refused_no_offer, |
| 3595 | self.agents.refused_full, | 3584 | self.agents.refused_full, |
| 3596 | }, | 3585 | }, |