src/os/server_os.zig
Ref: Size: 26.6 KiB History
//! The daemon's platform layer: every call whose spelling or existence
//! differs by OS, behind one name each. This root is the CONTRACT — a doc
//! comment per operation says what it guarantees and which failure it
//! prevents — and a child per OS spells the syscalls. A build for an OS
//! with no child is a compile error here, never a runtime surprise.
//!
//! Imports nothing of ours: the daemon, the pty and the CLI entry import
//! this, and folder rule 7 (build.zig) bans the raw spellings everywhere
//! else, so a new Linux-ism has one place to go.
const std = @import("std");
const builtin = @import("builtin");
pub const impl = switch (builtin.os.tag) {
.linux => @import("server_os_linux.zig"),
.macos => @import("server_os_macos.zig"),
else => @compileError("mux has no server platform arm for " ++ @tagName(builtin.os.tag)),
};
/// This process's pid, for the pid-named directories the daemon's
/// successor reaps (`xdg.reapDeadPid`).
pub fn getpid() std.posix.pid_t {
return impl.getpid();
}
/// Who is on the other end of a unix socket, or null when the kernel will
/// not say (across a pid namespace, for one); callers then rely on socket
/// shutdown. The daemon uses the pid to wait for a client that vanished.
/// "Will not say" is THIS root's rule and not an arm's: a kernel that
/// answers at all still reports a pid of 0 for a peer it cannot name, so
/// the non-positive pid is rejected here and an arm returns what it read.
pub const PeerCred = struct { uid: std.posix.uid_t, pid: std.posix.pid_t };
pub fn peerCred(fd: std.posix.socket_t) ?PeerCred {
const cred = impl.peerCred(fd) orelse return null;
if (cred.pid <= 0) return null;
return cred;
}
/// A NON-BLOCKING send that cannot raise SIGPIPE: a client that hung up
/// mid-frame is an error the pump handles, never a signal that ends the
/// daemon, and a client that stopped reading must not stall the pump
/// either. The daemon also ignores SIGPIPE process-wide; this is the half
/// that does not depend on the order of that ignore against a fork.
/// The name says NoWait because `client_os.sendNoSig` is the client-side
/// operation and it BLOCKS: the two differ in that one respect, and a
/// shared name would let a caller that moved between them assume the
/// other's behaviour.
/// An arm may make the fd itself non-blocking to keep that promise, and
/// that change is permanent for the fd — Darwin's does, because no send
/// flag can reach the wait it has to skip. Every fd the daemon sends on is
/// already non-blocking from its accept, so nothing of ours notices; a
/// caller that hands in a blocking fd and later expects blocking reads or
/// writes on it would.
pub fn sendNoSigNoWait(fd: std.posix.socket_t, bytes: []const u8) std.posix.SendError!usize {
return impl.sendNoSigNoWait(fd, bytes);
}
/// The socket type of an fd, for refusing to adopt a stream fd as the
/// QUIC listener across an upgrade: a stream fd would accept a handshake
/// and then lose every packet to recvfrom.
pub fn sockType(fd: std.posix.fd_t) error{NotASocket}!u32 {
return impl.sockType(fd);
}
pub const Winsize = std.posix.winsize;
pub const ForkedPty = struct { pid: std.posix.pid_t, master: std.posix.fd_t };
/// Fork with a fresh pty as the child's controlling terminal, sized before
/// the shell's first read so no program sees a 0x0 grid. Returns pid 0 in
/// the child, exactly as forkpty(3) does, so the child code that resets
/// signals and injects env stays where the fork is visible (pty.zig).
pub fn forkPty(ws: Winsize) error{ForkPtyFailed}!ForkedPty {
return impl.forkPty(ws);
}
/// A child's bail-out. Never `std.process.exit`: under link_libc that is
/// exit(3), which runs atexit and flushes stdio buffers the child inherited
/// from the parent — so the parent's pending bytes would be written twice.
pub fn exitNow(code: u8) noreturn {
impl.exitNow(code);
}
/// The repository's ONE fork that is not a pty: `mux d start -d`. The child
/// becomes a session leader, wires stdin to `stdin_fd` and both stdout and
/// stderr to `out_fd`, and execs `exe` with `argv` — a fresh image, because
/// `std.debug.MemoryAccessor` caches the pid it reads memory through and a
/// Debug child that kept running would inspect the parent and panic
/// (decisions.md, 2026-08-28). A failed exec exits 127 with no atexit.
/// Returns the child's pid; the parent decides how long to wait for it.
pub fn forkDetached(
exe: [*:0]const u8,
argv: [*:null]const ?[*:0]const u8,
stdin_fd: std.posix.fd_t,
out_fd: std.posix.fd_t,
) error{ForkFailed}!std.posix.pid_t {
return impl.forkDetached(exe, argv, stdin_fd, out_fd);
}
/// The fd barrier: every descriptor at or above `first` is closed in the
/// child before exec. CLOEXEC is set fd by fd, and an upgrade clears every
/// one and must seal them again — two hand-kept lists that would have to
/// agree, or the manifest carrier with the QUIC key bytes rides into the
/// shell. This needs no list.
pub fn closeFrom(first: std.posix.fd_t) void {
impl.closeFrom(first);
}
/// The two line-discipline bits that decide who echoes a keystroke, read
/// off the MASTER. Polled — the kernel notifies nobody when a mode changes.
pub const PtyMode = struct { icanon: bool, echo: bool };
pub fn ptyMode(master: std.posix.fd_t) std.posix.TermiosGetError!PtyMode {
return impl.ptyMode(master);
}
/// Foreground process group of the pty. Equal to the session's child pid
/// means no foreground job: the kernel's "command returned" with zero shell
/// cooperation, which is `mux a`'s `pgid` mechanism.
pub fn ptyFgPgid(master: std.posix.fd_t) error{IoctlFailed}!std.posix.pid_t {
return impl.ptyFgPgid(master);
}
/// Resize the pty; the kernel raises SIGWINCH in the session.
pub fn setWinsize(master: std.posix.fd_t, ws: Winsize) error{IoctlFailed}!void {
return impl.setWinsize(master, ws);
}
/// The upgrade manifest's carrier across `mux d upgrade`'s exec: an fd that
/// no path names once this returns, readable only by this uid, and NOT
/// CLOEXEC because the candidate must inherit it. It carries the QUIC arm's
/// raw key bytes, which is why "no path" is the property and not a nicety —
/// and why `closeFrom` seals it away from every session shell.
pub fn anonFd(name: [*:0]const u8) error{CarrierFailed}!std.posix.fd_t {
return impl.anonFd(name);
}
/// Identity of a file: the pair a rename-over changes and a rebuild in
/// place does not. Both halves matter — an inode number is only unique
/// within one filesystem, so an install that moved the image onto a
/// different mount can hand the new file the old file's inode number, and
/// a comparison by inode alone would call that daemon current.
const ImageIdent = struct { dev: u64, ino: u64 };
fn imageIdent(path: []const u8) !ImageIdent {
// stat, not open-then-fstat: a stat needs only search permission on the
// directories, so an image installed mode 0111 is still gradeable
// rather than reported stale forever. `std.posix.fstatat` rather than
// `std.fs.cwd().statFile`, because the posix `Stat` reports the device
// and 0.15.2's `std.fs.File.Stat` does not.
const st = try std.posix.fstatat(std.posix.AT.FDCWD, path, 0);
return .{ .dev = @intCast(st.dev), .ino = @intCast(st.ino) };
}
/// Stale when the path now names a different inode than `at_boot`, or
/// nothing at all: `make install` and `mux d upgrade HOST` both rename a
/// new file over the running image, and the daemon keeps executing the
/// old one. A null `at_boot` is the born-stale case — the path named
/// nothing when this process started — and stays stale whatever the path
/// holds now, because a file that landed there afterwards is somebody
/// else's image and not the one being executed.
fn staleAgainst(at_boot: ?ImageIdent, path: []const u8) bool {
const boot = at_boot orelse return true;
const now = imageIdent(path) catch return true;
return now.ino != boot.ino or now.dev != boot.dev;
}
/// The path this process was started from and what that path held at the
/// time. A null `ident` means it held nothing: the record is still kept,
/// because knowing WHICH path is what separates "born on an unlinked
/// image" from "this OS would not name my path at all".
const BootImage = struct {
ident: ?ImageIdent,
path: [std.fs.max_path_bytes]u8,
len: usize,
fn spelling(self: *const BootImage) []const u8 {
return self.path[0..self.len];
}
};
fn recordImage(path: []const u8) BootImage {
var rec: BootImage = .{ .ident = imageIdent(path) catch null, .path = undefined, .len = path.len };
@memcpy(rec.path[0..path.len], path);
return rec;
}
var boot_image: ?BootImage = null;
/// Record the running image's path and identity. Called once at daemon
/// start; a later call is a no-op, so the comparison is always against
/// boot and a rename that lands a file at the path afterwards can never
/// promote a stale daemon back to current.
///
/// The two failures are not the same answer. A path this OS will not name
/// at all (`selfExePath` fails) records NOTHING and `selfImageStale` then
/// answers false — unknown is not stale, because a wall must not dress a
/// healthy box in a warning over a refused readlink. A path that IS named
/// but holds nothing (Linux spells a deleted image `…/mux (deleted)`, which
/// is how `spawn.selfExe`'s `/proc/self/exe` fallback boots a daemon) is a
/// daemon already running an image no path holds: that records the path
/// with no ident, and every ask answers stale.
pub fn noteBootImage() void {
if (boot_image != null) return;
var buf: [std.fs.max_path_bytes]u8 = undefined;
const p = std.fs.selfExePath(&buf) catch return;
boot_image = recordImage(p);
}
/// Has the file at the running image's path been replaced since boot.
/// Read fresh per ask: a rename lands under a running daemon at any moment,
/// and one stat per `sessions_req` is nothing. Implemented here and in no
/// arm, because it is the same rule on every OS an arm could be written for.
pub fn selfImageStale() bool {
noteBootImage();
if (boot_image) |*b| return staleAgainst(b.ident, b.spelling());
return false;
}
test "server_os: the arm compiles and answers for the process it is in" {
try std.testing.expect(getpid() > 0);
}
test "server_os.peerCred: the kernel names the peer of a socketpair as this process" {
var sp: [2]std.posix.fd_t = undefined;
try std.testing.expectEqual(@as(c_int, 0), std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp));
defer std.posix.close(sp[0]);
defer std.posix.close(sp[1]);
const cred = peerCred(sp[0]) orelse return error.NoCred;
try std.testing.expectEqual(getpid(), cred.pid);
try std.testing.expectEqual(std.c.geteuid(), cred.uid);
}
test "server_os.closeFrom: a fd below the floor survives and one above does not" {
// pipe(2) sets no CLOEXEC, so a child that did not close would still
// hold pipe[1]. Asked through /dev/fd, which both OSes have.
const pipe = try std.posix.pipe();
defer std.posix.close(pipe[0]);
defer std.posix.close(pipe[1]);
var cmd_buf: [96]u8 = undefined;
const cmd = try std.fmt.bufPrintZ(&cmd_buf, "test -e /dev/fd/{d} && exit 3; exit 0", .{pipe[1]});
const argv = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd.ptr };
const ws: Winsize = .{ .row = 24, .col = 80, .xpixel = 0, .ypixel = 0 };
const f = try forkPty(ws);
if (f.pid == 0) {
closeFrom(3);
std.posix.execveZ(argv[0].?, &argv, std.c.environ) catch {};
exitNow(127);
}
defer std.posix.close(f.master);
const r = std.posix.waitpid(f.pid, 0);
try std.testing.expect(std.posix.W.IFEXITED(r.status));
try std.testing.expectEqual(@as(u32, 0), std.posix.W.EXITSTATUS(r.status));
}
test "server_os.setWinsize then ptyMode: the master answers about the line discipline" {
const ws: Winsize = .{ .row = 31, .col = 101, .xpixel = 0, .ypixel = 0 };
const argv = [_:null]?[*:0]const u8{ "/bin/sh", "-c", "stty -echo; sleep 5" };
const f = try forkPty(ws);
if (f.pid == 0) {
std.posix.execveZ(argv[0].?, &argv, std.c.environ) catch {};
exitNow(127);
}
defer {
std.posix.kill(f.pid, std.posix.SIG.KILL) catch {};
_ = std.posix.waitpid(f.pid, 0);
std.posix.close(f.master);
}
// A fresh pty echoes; the shell turns it off. Polled, because nothing
// notifies a mode change.
var waited: usize = 0;
while (waited < 100) : (waited += 1) {
const m = try ptyMode(f.master);
if (!m.echo) break;
std.Thread.sleep(50 * std.time.ns_per_ms);
}
try std.testing.expect(!(try ptyMode(f.master)).echo);
// The foreground group is the shell itself while `sleep` is its child
// in the same group: fgPgid equals the pid forkPty returned.
try std.testing.expectEqual(f.pid, try ptyFgPgid(f.master));
try setWinsize(f.master, .{ .row = 10, .col = 40, .xpixel = 0, .ypixel = 0 });
}
test "server_os.forkDetached: the child is a session leader writing to the fd it was given" {
// Asked of the OS, and through `getsid(2)` rather than `ps`: the session
// id is what "detached" means, and only procps prints it — macOS's ps
// has no `sid` column at all, and its `sess` column is the kernel
// address of the session, which reads 0 for anyone but root (measured
// 2026-09-03), so a `ps` claim there compares 0 against 0 and passes
// whatever the child did. `getsid` is POSIX and answers the number on
// both. The child still prints its own pid, so the claim is the whole
// one: the process this returned a pid for is a session LEADER (its sid
// is its pid) in a session that is not the caller's.
//
// `getsid` needs the child ALIVE, not merely unreaped: Darwin answers -1
// for a zombie where Linux still names its session (measured 2026-09-03
// on macOS 26). So `stdin_fd` is a pipe rather than /dev/null, and the
// child echoes a word out of it and then blocks reading a second — which
// holds it still for the question AND makes the stdin argument
// load-bearing. The echo is what pins stdin, not the block: a child
// wired to the wrong fd also ends early, but WHEN it ends is a race this
// test would win most of the time, and /dev/null pinned nothing at all.
const libc = struct {
extern "c" fn getsid(pid: std.posix.pid_t) std.posix.pid_t;
};
// CLOEXEC on both pipes: `forkDetached` closes nothing before it execs,
// so a plain pipe would leave the CHILD holding the write end of its own
// stdin and its `read` would never see the EOF this test closes for.
// The two ends it is given survive anyway, because dup2 clears the flag
// on the descriptor it writes.
const to_child = try std.posix.pipe2(.{ .CLOEXEC = true });
const from_child = try std.posix.pipe2(.{ .CLOEXEC = true });
defer std.posix.close(from_child[0]);
// Written BEFORE the fork, so the word is waiting in the pipe and the
// first read cannot block on this test's own ordering.
try std.testing.expectEqual(@as(usize, 5), try std.posix.write(to_child[1], "ping\n"));
const argv = [_:null]?[*:0]const u8{ "/bin/sh", "-c", "echo $$; read word; echo $word; read hold" };
const pid = try forkDetached("/bin/sh", &argv, to_child[0], from_child[1]);
std.posix.close(to_child[0]);
std.posix.close(from_child[1]);
// To the second newline and no further: the child is holding its stdout
// open on purpose, so a read to EOF here would wait for an exit this test
// has not asked for yet.
var buf: [128]u8 = undefined;
var n: usize = 0;
while (std.mem.count(u8, buf[0..n], "\n") < 2) {
const got = try std.posix.read(from_child[0], buf[n..]);
if (got == 0) return error.NoOutput;
n += got;
}
const child_sid = libc.getsid(pid);
const own_sid = libc.getsid(0);
// The child's second `read` returns only when this end goes.
std.posix.close(to_child[1]);
_ = std.posix.waitpid(pid, 0);
var lines = std.mem.tokenizeScalar(u8, buf[0..n], '\n');
const shpid = lines.next() orelse return error.NoOutput;
const echoed = lines.next() orelse return error.NoOutput;
try std.testing.expectEqual(pid, try std.fmt.parseInt(std.posix.pid_t, shpid, 10));
try std.testing.expectEqualStrings("ping", echoed);
try std.testing.expect(own_sid > 0);
try std.testing.expectEqual(pid, child_sid);
try std.testing.expect(child_sid != own_sid);
}
test "server_os.selfImageStale: a rename over the image's path is stale, an untouched path is not" {
// The test binary cannot be renamed under itself safely, so the rule is
// exercised on a copy in a temp dir through the same two functions with
// the path named explicitly.
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
try tmp.dir.writeFile(.{ .sub_path = "img", .data = "v1" });
var pbuf: [std.fs.max_path_bytes]u8 = undefined;
const path = try tmp.dir.realpath("img", &pbuf);
var ident = try imageIdent(path);
try std.testing.expect(!staleAgainst(ident, path));
try tmp.dir.writeFile(.{ .sub_path = "img.new", .data = "v2" });
try tmp.dir.rename("img.new", "img");
try std.testing.expect(staleAgainst(ident, path));
ident = try imageIdent(path);
try std.testing.expect(!staleAgainst(ident, path));
try tmp.dir.deleteFile("img");
try std.testing.expect(staleAgainst(ident, path));
}
test "server_os.selfImageStale: a daemon born on an unlinked image never reads healthy" {
// The route on Linux: `spawn.selfExe` hands the daemon `/proc/self/exe`
// exactly when the resolved path is gone, so the process can boot on an
// image no path names. `selfExePath` still SPELLS that path (with the
// kernel's suffix), and a spelled path that stats to nothing must read
// stale — the old suffix check said so, and a false "healthy" is the one
// verdict this check exists to never give.
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
try tmp.dir.writeFile(.{ .sub_path = "img", .data = "v1" });
var pbuf: [std.fs.max_path_bytes]u8 = undefined;
const path = try tmp.dir.realpath("img", &pbuf);
try tmp.dir.deleteFile("img");
const born = recordImage(path);
try std.testing.expect(born.ident == null);
try std.testing.expect(staleAgainst(born.ident, born.spelling()));
// A later file at that path is somebody else's image, not the one this
// process is executing, so the verdict does not go back to healthy.
try tmp.dir.writeFile(.{ .sub_path = "img", .data = "v2" });
try std.testing.expect(staleAgainst(born.ident, born.spelling()));
}
test "server_os.noteBootImage: the live binary is its own image, and a second call is a no-op" {
// The cheap in-process pin, through the PUBLIC pair: this test binary was
// not renamed under itself, so it must read healthy however many times
// the record is asked for.
noteBootImage();
noteBootImage();
try std.testing.expect(!selfImageStale());
}
test "server_os.anonFd: no path names it, and it is not CLOEXEC" {
const fd = try anonFd("mux-test-carrier");
defer std.posix.close(fd);
const st = try std.posix.fstat(fd);
try std.testing.expectEqual(@as(@TypeOf(st.nlink), 0), st.nlink);
const flags = try std.posix.fcntl(fd, std.posix.F.GETFD, 0);
try std.testing.expectEqual(@as(usize, 0), flags & std.posix.FD_CLOEXEC);
try std.posix.lseek_SET(fd, 0);
_ = try std.posix.write(fd, "abc");
try std.posix.lseek_SET(fd, 0);
var buf: [3]u8 = undefined;
try std.testing.expectEqual(@as(usize, 3), try std.posix.read(fd, &buf));
try std.testing.expectEqualStrings("abc", &buf);
}
test "server_os.sendNoSigNoWait: a closed peer is an error, not a signal" {
// Judged in a CHILD, because this process cannot be asked. Zig's own
// startup code installs a no-op SIGPIPE handler in every binary it
// starts, the test runner included, so a plain send with no
// MSG_NOSIGNAL also returns BrokenPipe here — a test written in this
// process stays green with the flag deleted, which is the one mistake
// it exists to catch. The child puts SIGPIPE back at SIG_DFL first, so
// a send that raises the signal DIES and the parent reads a status that
// never exited. `forkPty` rather than a raw fork because of the child's
// stdio: a raw-forked child shares fd 1 with this process, and fd 1 of
// a test binary is the build runner's protocol stream, which one stray
// byte wedges. A `forkPty` child gets its own stdio on the slave, so
// anything it prints goes to a pty nobody reads.
const f = try forkPty(.{ .row = 24, .col = 80, .xpixel = 0, .ypixel = 0 });
if (f.pid == 0) {
var dfl: std.posix.Sigaction = .{
.handler = .{ .handler = std.posix.SIG.DFL },
.mask = std.posix.sigemptyset(),
.flags = 0,
};
std.posix.sigaction(std.posix.SIG.PIPE, &dfl, null);
// Two legs, because "the peer is gone" is two different states to
// the kernel and only the second is the one the daemon meets. A
// socket that was NEVER written to and then lost its peer is the
// easy case; a socket that carried frames and then lost its peer
// mid-stream is the pump's own sequence, and on Darwin the two
// differ — the flag that suppresses the signal is a socket option
// there, and a socket the kernel has already shut down refuses to
// take one (see server_os_macos.sendNoSigNoWait). A test that asked
// only the first would pass on an arm that can never arm a live
// socket, and one that asked only the second would pass on an arm
// that only ever works after a successful send.
//
// A send that SUCCEEDED to a closed peer is as wrong as one that
// signalled, and neither is 0.
var gone: [2]std.posix.fd_t = undefined;
if (std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &gone) != 0) exitNow(2);
std.posix.close(gone[1]);
if (sendNoSigNoWait(gone[0], "x")) |_| exitNow(2) else |e| if (e != error.BrokenPipe) exitNow(2);
var live: [2]std.posix.fd_t = undefined;
if (std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &live) != 0) exitNow(2);
_ = sendNoSigNoWait(live[0], "x") catch exitNow(2);
std.posix.close(live[1]);
if (sendNoSigNoWait(live[0], "x")) |_| exitNow(2) else |e| if (e != error.BrokenPipe) exitNow(2);
exitNow(0);
}
defer std.posix.close(f.master);
const status = std.posix.waitpid(f.pid, 0).status;
try std.testing.expect(std.posix.W.IFEXITED(status));
try std.testing.expectEqual(@as(u32, 0), std.posix.W.EXITSTATUS(status));
var sp: [2]std.posix.fd_t = undefined;
try std.testing.expectEqual(@as(c_int, 0), std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp));
defer std.posix.close(sp[0]);
defer std.posix.close(sp[1]);
try std.testing.expectEqual(@as(u32, std.posix.SOCK.STREAM), try sockType(sp[0]));
}
test "server_os.sendNoSigNoWait: a full buffer is WouldBlock, not a stall" {
// The NoWait half of the name, asked of a socket nobody made
// non-blocking. Linux answers it from MSG_DONTWAIT alone. Darwin does
// not: xnu consults that flag when it takes the socket buffer lock, but
// the wait for buffer SPACE tests the socket's own SS_NBIO bit, which is
// O_NONBLOCK on the file descriptor and nothing a send flag can reach.
// An arm that only passes the flag therefore SLEEPS here, waiting for a
// peer that never reads — the same stall a client that stopped reading
// would impose on the daemon's only pump. The watchdog below is what
// turns that sleep into a sentence.
//
// The daemon's own fds are all non-blocking from their accept
// (`Server.setNonblocking`), so this asks the operation the question the
// daemon cannot: the promise has to hold for the fd, not for the caller.
var sp: [2]std.posix.fd_t = undefined;
try std.testing.expectEqual(@as(c_int, 0), std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp));
defer std.posix.close(sp[0]);
defer std.posix.close(sp[1]);
// A WATCHDOG, because the failure this test exists to catch is a thread
// that never runs again: an arm that waits for the peer sleeps inside
// `send`, so no deadline checked between iterations would ever be read.
// Another thread has to be the one holding the clock. It cannot unblock
// the send — draining the peer would make the test pass for the wrong
// reason — so it prints the diagnosis and ends the process, which the
// build runner reports as a failed test command. That is a sentence
// naming the cause instead of a runner that goes quiet for its whole
// timeout, which CLAUDE.md names as the worst failure mode here.
const Watchdog = struct {
done: std.atomic.Value(bool) = .init(false),
fn run(self: *@This()) void {
var waited_ms: usize = 0;
// Ten seconds against a loop of at most a few hundred syscalls:
// slack enough that a loaded machine cannot trip it, short enough
// to be an answer rather than a wait.
while (waited_ms < 10_000) : (waited_ms += 50) {
if (self.done.load(.acquire)) return;
std.Thread.sleep(50 * std.time.ns_per_ms);
}
std.debug.print(
\\
\\server_os.sendNoSigNoWait blocked on a full send buffer instead of
\\answering WouldBlock, so the NoWait half of its name is not true on
\\this OS. On Darwin that is MSG_DONTWAIT without O_NONBLOCK on the fd:
\\xnu tests the socket's own SS_NBIO bit when it waits for buffer space.
\\
, .{});
exitNow(1);
}
};
var watchdog: Watchdog = .{};
const watcher = try std.Thread.spawn(.{}, Watchdog.run, .{&watchdog});
defer {
watchdog.done.store(true, .release);
watcher.join();
}
// Nobody ever reads sp[1]. A socket send buffer is a few hundred KB at
// most, so 32 MB of 64 KB writes is two orders of magnitude of slack and
// still finishes in well under a second; reaching the bound means the
// send is swallowing the fill instead of reporting it.
const chunk = [_]u8{'x'} ** (64 * 1024);
var sent: usize = 0;
while (sent < 32 << 20) {
const n = sendNoSigNoWait(sp[0], &chunk) catch |e| {
if (e != error.WouldBlock) return e;
return;
};
sent += n;
}
return error.SendNeverReportedAFullBuffer;
}
// Forces semantic analysis of every pub decl under `zig build test`, so an
// unreferenced operation must at least compile for this OS.
test {
std.testing.refAllDeclsRecursive(@This());
}