5d4461e0
Build the test socket pair with socketpair(2)
a73x 2026-08-31 14:39
Commit message
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 | ||