a73x

5d4461e0

Build the test socket pair with socketpair(2)

a73x   2026-08-31 14:39

Commit message
Build the test socket pair with socketpair(2)

`connectedPair` bound a unix listener in the test's tmpDir, dialled it,
accepted, then unlinked the path — nine lines and a connect-before-accept
ordering subtlety, for a pair of fds nobody ever addresses by name. The
17 callers each had to invent a unique name ("stall-a", "cap-c") purely
so two pairs in one directory would not collide.

`std.os.linux.socketpair` does the same thing in one syscall. The old
comment said `std.posix` has no socketpair on 0.15.2, which is true and
was never the reason: this is a Linux-only program that already spells
`std.os.linux.syscall3` in `pty.zig`. A comment recording that nobody
tried the obvious alternative is a TODO, so the alternative is taken and
the sentence is gone.

What stays is the half that is a real constraint: the pair must be
SOCKETS and not pipes, because `std.posix.send` maps ENOTSOCK to
`unreachable` and panics rather than returning an error.

Carries two comment restorations in the files this already touches: the
harness fixture's measured figures for why its shell neither sleeps nor
reads (791 ms past SIGKILL, 99 ticks/s), and `collectGapReplay`'s two
unexplained loop bounds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

src/server/server_test_attach.zig
Old New
@@ -1263,7 +1263,7 @@ test "Server: injected bytes reach frame handling, split anywhere" {
1263 1263
1264 // A real socket pair so the slot behaves like any other client: the 1264 // A real socket pair so the slot behaves like any other client: the
1265 // point of this test is the INBOUND route, not the outbound one. 1265 // point of this test is the INBOUND route, not the outbound one.
1266 const c = try connectedPair(dir_path, "inject-c"); 1266 const c = try connectedPair();
1267 defer std.posix.close(c.peer); 1267 defer std.posix.close(c.peer);
1268 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 1268 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
1269 1269
@@ -1329,7 +1329,7 @@ test "Server: a client that sends half a frame does not stall the pump" {
1329 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); 1329 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" });
1330 defer srv.deinit(); 1330 defer srv.deinit();
1331 1331
1332 const c = try connectedPair(dir_path, "half-c"); 1332 const c = try connectedPair();
1333 defer std.posix.close(c.peer); 1333 defer std.posix.close(c.peer);
1334 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 1334 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
1335 1335
@@ -1601,7 +1601,7 @@ test "Server: a slot promoted before it attached enters the activity order at it
1601 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); 1601 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" });
1602 defer srv.deinit(); 1602 defer srv.deinit();
1603 1603
1604 const c = try connectedPair(dir_path, "quicorder-c"); 1604 const c = try connectedPair();
1605 defer std.posix.close(c.peer); 1605 defer std.posix.close(c.peer);
1606 // A client slot with no session yet, which is how a QUIC client starts 1606 // A client slot with no session yet, which is how a QUIC client starts
1607 // life: quicOnOpen seats it when the handshake completes, so its FIRST 1607 // life: quicOnOpen seats it when the handshake completes, so its FIRST
src/server/server_test_await.zig
Old New
@@ -1018,7 +1018,7 @@ test "Server: a promoted-but-unattached slot receives nothing" {
1018 // a live client slot with no session. Connection = session means such 1018 // a live client slot with no session. Connection = session means such
1019 // a slot receives nothing — no deltas, no snapshots, no mode bits — 1019 // a slot receives nothing — no deltas, no snapshots, no mode bits —
1020 // until an attach says which session it is asking about. 1020 // until an attach says which session it is asking about.
1021 const c = try connectedPair(dir_path, "limbo-c"); 1021 const c = try connectedPair();
1022 defer std.posix.close(c.peer); 1022 defer std.posix.close(c.peer);
1023 srv.clients[0] = .{ .sink = .{ .socket = c.daemon } }; 1023 srv.clients[0] = .{ .sink = .{ .socket = c.daemon } };
1024 1024
src/server/server_test_clipboard.zig
Old New
@@ -50,7 +50,7 @@ test "Server: a session-less slot still answers a well-formed selection request"
50 // The QUIC shape again: promoted by a completed handshake, never 50 // The QUIC shape again: promoted by a completed handshake, never
51 // attached. It has no grid to read, but its request carried an id, so 51 // attached. It has no grid to read, but its request carried an id, so
52 // the lane owes it exactly one correlated answer. 52 // the lane owes it exactly one correlated answer.
53 const c = try connectedPair(dir_path, "selless-c"); 53 const c = try connectedPair();
54 defer std.posix.close(c.peer); 54 defer std.posix.close(c.peer);
55 srv.clients[0] = .{ .sink = .{ .socket = c.daemon } }; 55 srv.clients[0] = .{ .sink = .{ .socket = c.daemon } };
56 56
@@ -80,7 +80,7 @@ test "Server: an unencodable selection result still answers with unavailable" {
80 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); 80 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" });
81 defer srv.deinit(); 81 defer srv.deinit();
82 82
83 const c = try connectedPair(dir_path, "seloom-c"); 83 const c = try connectedPair();
84 defer std.posix.close(c.peer); 84 defer std.posix.close(c.peer);
85 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 85 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
86 // The per-client queue must not be what fails: this test is about the 86 // The per-client queue must not be what fails: this test is about the
@@ -303,6 +303,10 @@ const GapReplay = struct {
303 /// for — so "wait for the content frame, then watch for a term_event" is blind 303 /// for — so "wait for the content frame, then watch for a term_event" is blind
304 /// to an event queued AHEAD of it, which is exactly where a replay hoisted out 304 /// to an event queued AHEAD of it, which is exactly where a replay hoisted out
305 /// of the delta branch lands. 305 /// of the delta branch lands.
306 ///
307 /// The 400 below is `awaitFrame`'s own budget for an attach reply, ~6ms an
308 /// iteration; the 60 is the settling window that keeps running past the content
309 /// frame, so an event queued after it is caught too.
306 fn collectGapReplay(alloc: std.mem.Allocator, srv: *Server, fd: std.posix.fd_t) !GapReplay { 310 fn collectGapReplay(alloc: std.mem.Allocator, srv: *Server, fd: std.posix.fd_t) !GapReplay {
307 var out: GapReplay = .{}; 311 var out: GapReplay = .{};
308 var i: usize = 0; 312 var i: usize = 0;
src/server/server_test_deliver.zig
Old New
@@ -33,10 +33,10 @@ test "Server: a stalled client does not block delivery to others" {
33 33
34 // A never reads a byte, so its send buffer fills and stays full. B 34 // A never reads a byte, so its send buffer fills and stays full. B
35 // drains normally. srv.deinit() closes both daemon-side fds. 35 // drains normally. srv.deinit() closes both daemon-side fds.
36 const a = try connectedPair(dir_path, "stall-a"); 36 const a = try connectedPair();
37 defer std.posix.close(a.peer); 37 defer std.posix.close(a.peer);
38 try shrinkSendBuf(a.daemon); 38 try shrinkSendBuf(a.daemon);
39 const b = try connectedPair(dir_path, "stall-b"); 39 const b = try connectedPair();
40 defer std.posix.close(b.peer); 40 defer std.posix.close(b.peer);
41 41
42 srv.clients[0] = .{ .sink = .{ .socket = a.daemon }, .session = 0 }; 42 srv.clients[0] = .{ .sink = .{ .socket = a.daemon }, .session = 0 };
@@ -88,7 +88,7 @@ test "Server: a client exceeding the pending cap is dropped" {
88 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); 88 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" });
89 defer srv.deinit(); 89 defer srv.deinit();
90 90
91 const c = try connectedPair(dir_path, "cap-c"); 91 const c = try connectedPair();
92 defer std.posix.close(c.peer); 92 defer std.posix.close(c.peer);
93 try shrinkSendBuf(c.daemon); 93 try shrinkSendBuf(c.daemon);
94 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 94 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
@@ -156,7 +156,7 @@ test "Server: a writable backlog is flushed by poll, not mistaken for input" {
156 _ = std.posix.read(srv.sessions.table[0].?.pty.master, &buf) catch break; 156 _ = std.posix.read(srv.sessions.table[0].?.pty.master, &buf) catch break;
157 } 157 }
158 158
159 const c = try connectedPair(dir_path, "pollout-c"); 159 const c = try connectedPair();
160 defer std.posix.close(c.peer); 160 defer std.posix.close(c.peer);
161 try shrinkSendBuf(c.daemon); 161 try shrinkSendBuf(c.daemon);
162 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 162 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
@@ -196,7 +196,7 @@ test "Server: a partially flushed queue delivers every byte exactly once" {
196 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); 196 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" });
197 defer srv.deinit(); 197 defer srv.deinit();
198 198
199 const c = try connectedPair(dir_path, "pf-c"); 199 const c = try connectedPair();
200 defer std.posix.close(c.peer); 200 defer std.posix.close(c.peer);
201 try shrinkSendBuf(c.daemon); 201 try shrinkSendBuf(c.daemon);
202 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 202 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
@@ -351,7 +351,7 @@ test "Server: the exit drain delivers a backlog once the peer resumes reading" {
351 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); 351 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" });
352 defer srv.deinit(); 352 defer srv.deinit();
353 353
354 const c = try connectedPair(dir_path, "exitdrain-c"); 354 const c = try connectedPair();
355 defer std.posix.close(c.peer); 355 defer std.posix.close(c.peer);
356 try shrinkSendBuf(c.daemon); 356 try shrinkSendBuf(c.daemon);
357 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 357 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
@@ -402,7 +402,7 @@ test "Server: the exit drain gives up on a peer that never reads" {
402 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" }); 402 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/sh" });
403 defer srv.deinit(); 403 defer srv.deinit();
404 404
405 const c = try connectedPair(dir_path, "drain-c"); 405 const c = try connectedPair();
406 defer std.posix.close(c.peer); 406 defer std.posix.close(c.peer);
407 try shrinkSendBuf(c.daemon); 407 try shrinkSendBuf(c.daemon);
408 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 }; 408 srv.clients[0] = .{ .sink = .{ .socket = c.daemon }, .session = 0 };
@@ -444,9 +444,9 @@ test "Server: broadcast stats count every send but the counterfactual once" {
444 // Two stand-in clients, driving the server directly so the accounting is 444 // Two stand-in clients, driving the server directly so the accounting is
445 // exact instead of hostage to shell timing. Real sockets, not pipes: the 445 // exact instead of hostage to shell timing. Real sockets, not pipes: the
446 // send path is send(2) now. srv.deinit() closes the daemon-side fds. 446 // send path is send(2) now. srv.deinit() closes the daemon-side fds.
447 const pa = try connectedPair(dir_path, "stats-a"); 447 const pa = try connectedPair();
448 defer std.posix.close(pa.peer); 448 defer std.posix.close(pa.peer);
449 const pb = try connectedPair(dir_path, "stats-b"); 449 const pb = try connectedPair();
450 defer std.posix.close(pb.peer); 450 defer std.posix.close(pb.peer);
451 srv.clients[0] = .{ .sink = .{ .socket = pa.daemon }, .session = 0 }; 451 srv.clients[0] = .{ .sink = .{ .socket = pa.daemon }, .session = 0 };
452 srv.clients[1] = .{ .sink = .{ .socket = pb.daemon }, .session = 0 }; 452 srv.clients[1] = .{ .sink = .{ .socket = pb.daemon }, .session = 0 };
src/server/server_test_harness.zig
Old New
@@ -26,26 +26,21 @@ pub fn applyFrame(alloc: std.mem.Allocator, replica: *Engine, frame: proto.Frame
26 } 26 }
27 27
28 /// A connected pair of unix stream sockets, standing in for an attached client. 28 /// A connected pair of unix stream sockets, standing in for an attached client.
29 /// `daemon` goes in a client slot; `peer` is the client's end.
30 ///
29 /// SOCKETS rather than pipes because the client send path is `send(2)`, which on 31 /// SOCKETS rather than pipes because the client send path is `send(2)`, which on
30 /// a pipe fd fails ENOTSOCK — and `std.posix.send` maps that errno to 32 /// a pipe fd fails ENOTSOCK — and `std.posix.send` maps that errno to
31 /// `unreachable`. `std.posix` has no socketpair on 0.15.2. 33 /// `unreachable`, so it panics rather than returning an error.
32 pub const SockPair = struct { daemon: std.posix.fd_t, peer: std.posix.fd_t }; 34 pub const SockPair = struct { daemon: std.posix.fd_t, peer: std.posix.fd_t };
33 35
34 pub fn connectedPair(dir_path: []const u8, name: []const u8) !SockPair { 36 pub fn connectedPair() !SockPair {
35 var buf: [256]u8 = undefined; 37 var fds: [2]i32 = undefined;
36 const path = try std.fmt.bufPrint(&buf, "{s}/{s}", .{ dir_path, name }); 38 const rc = std.os.linux.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds);
37 const addr = try std.net.Address.initUnix(path); 39 switch (std.posix.errno(rc)) {
38 var listener = try addr.listen(.{}); 40 .SUCCESS => {},
39 defer { 41 else => |e| return std.posix.unexpectedErrno(e),
40 listener.deinit();
41 std.fs.cwd().deleteFile(path) catch {};
42 } 42 }
43 // Connect before accept: the listen backlog completes the handshake, so 43 return .{ .daemon = fds[0], .peer = fds[1] };
44 // neither half of this blocks on the other.
45 const peer = try std.net.connectUnixSocket(path);
46 errdefer peer.close();
47 const conn = try listener.accept();
48 return .{ .daemon = conn.stream.handle, .peer = peer.handle };
49 } 44 }
50 45
51 /// Test helper: the first snapshot-or-delta frame to arrive, reduced to what 46 /// Test helper: the first snapshot-or-delta frame to arrive, reduced to what
@@ -224,10 +219,13 @@ pub fn writeDyingGapShell(alloc: std.mem.Allocator, tmp: *TmpDir) ![:0]u8 {
224 /// only thing left that can. What a bounded end has to survive. 219 /// only thing left that can. What a bounded end has to survive.
225 pub fn writeStubbornShell(alloc: std.mem.Allocator, tmp: *TmpDir) ![:0]u8 { 220 pub fn writeStubbornShell(alloc: std.mem.Allocator, tmp: *TmpDir) ![:0]u8 {
226 // Blocked in `open(2)` on a fifo nobody writes: no child to orphan, no spin. 221 // Blocked in `open(2)` on a fifo nobody writes: no child to orphan, no spin.
227 // A `sleep` loop leaves a `sleep` behind past the shell's SIGKILL, and 222 // A `sleep` loop leaves a `sleep` behind for up to its full second (measured:
228 // looping on `read` off the closed master burns a core on EOF. The loop 223 // 791 ms past the shell's SIGKILL), and looping on `read` off the closed
229 // stays as the fallback, so a box that cannot mkfifo still gets a stubborn 224 // master burns a whole core on EOF (measured: 99 ticks/s).
230 // shell rather than one that quietly exits. 225 //
226 // The loop stays as the fallback so a box that cannot mkfifo still gets a
227 // stubborn shell: a fixture that quietly exited here would let the
228 // bounded-end gates pass for the wrong reason.
231 const body = try std.fmt.allocPrint(alloc, 229 const body = try std.fmt.allocPrint(alloc,
232 \\#!/bin/sh 230 \\#!/bin/sh
233 \\trap '' TERM HUP 231 \\trap '' TERM HUP
src/server/server_test_modes.zig
Old New
@@ -51,7 +51,7 @@ test "Server: an attach arriving on an established connection is answered with t
51 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" }); 51 var srv = try Server.init(alloc, .{ .sock_path = sock_path, .shell = "/bin/cat" });
52 defer srv.deinit(); 52 defer srv.deinit();
53 53
54 const c = try connectedPair(dir_path, "reattach-c"); 54 const c = try connectedPair();
55 defer std.posix.close(c.peer); 55 defer std.posix.close(c.peer);
56 // Already a client before it has said a word — how a QUIC client starts life, 56 // Already a client before it has said a word — how a QUIC client starts life,
57 // since its slot is created at handshake and its FIRST attach reaches 57 // since its slot is created at handshake and its FIRST attach reaches
src/server/server_test_session.zig
Old New
@@ -1485,7 +1485,7 @@ test "Server: a session-less slot's status_req resolves the tail like an observe
1485 // a datagram would be. A `status_req` arm that `orelse return`s such a slot 1485 // a datagram would be. A `status_req` arm that `orelse return`s such a slot
1486 // makes `mux a status --quic` time out. Slot 1, because slot 0 is claimed 1486 // makes `mux a status --quic` time out. Slot 1, because slot 0 is claimed
1487 // and stomping it would leak its queued snapshot. 1487 // and stomping it would leak its queued snapshot.
1488 const c = try connectedPair(dir_path, "quicstatus-c"); 1488 const c = try connectedPair();
1489 defer std.posix.close(c.peer); 1489 defer std.posix.close(c.peer);
1490 srv.clients[1] = .{ .sink = .{ .socket = c.daemon } }; 1490 srv.clients[1] = .{ .sink = .{ .socket = c.daemon } };
1491 1491