a73x

19e5c3c9

refactor: Server.deinit tears down through teardownClient/dropObserver

a73x   2026-08-29 10:01

Commit message
refactor: Server.deinit tears down through teardownClient/dropObserver

deinit re-spelled both slot loops line for line. The two owners already
say how a client and an observer die; deinit now walks the tables through
them, so a new field in a slot cannot be freed on one path and leaked on
the other. The agent sweep stays first and silent, which makes
teardownClient's closeOfClient a no-op here.

Pinned by every server unit test: each one deinits a Server that held
clients, observers and channels, under the DebugAllocator's leak check.

src/server/server.zig
Old New
@@ -926,19 +926,8 @@ pub const Server = struct {
926 // the daemon is going. Every channel, not a per-session sweep: what 926 // the daemon is going. Every channel, not a per-session sweep: what
927 // has to happen here is that no descriptor outlives the table. 927 // has to happen here is that no descriptor outlives the table.
928 for (0..max_agent_chans) |s| self.agents.closeChan(self, s, .silent); 928 for (0..max_agent_chans) |s| self.agents.closeChan(self, s, .silent);
929 for (&self.clients) |*slot| { 929 for (0..self.clients.len) |i| self.teardownClient(i, true);
930 if (slot.*) |*c| { 930 for (0..self.observers.len) |i| self.dropObserver(i);
931 c.pending.deinit(self.alloc);
932 c.inbound.deinit(self.alloc);
933 c.sink.close();
934 }
935 }
936 for (&self.observers) |*slot| {
937 if (slot.*) |*o| {
938 o.inbound.deinit(self.alloc);
939 std.posix.close(o.fd);
940 }
941 }
942 // After the client slots, never before: a QUIC sink closes its 931 // After the client slots, never before: a QUIC sink closes its
943 // connection THROUGH the listener, so the listener has to outlive 932 // connection THROUGH the listener, so the listener has to outlive
944 // the slots that hold it. That is the ordering main.zig's defers 933 // the slots that hold it. That is the ordering main.zig's defers