a73x

289e83a4

refactor: an observer's refusal is spelled once, not eight times

a73x   2026-08-29 01:10

Commit message
refactor: an observer's refusal is spelled once, not eight times

exit_status 1 + close had three copies and the upgrade refusal five, each
remembering the drop for itself.

src/server/server.zig
Old New
@@ -1962,6 +1962,25 @@ pub const Server = struct {
1962 } 1962 }
1963 } 1963 }
1964 1964
1965 /// exit_status 1 is the only "no" this wire has; the close follows it.
1966 fn refuseObserver(self: *Server, i: usize) void {
1967 self.replyTo(.{ .observer = i }, .exit_status, &.{1});
1968 self.dropObserver(i);
1969 }
1970
1971 /// An upgrade refusal: byte 1, then the reason in words, then the close
1972 /// — a `mux d upgrade` that will not happen has nothing further to say
1973 /// on this socket. Five sites spelled this out, each remembering the
1974 /// drop for itself.
1975 fn refuseUpgrade(self: *Server, i: usize, reason: []const u8) void {
1976 var reply: [256]u8 = undefined;
1977 reply[0] = 1;
1978 const n = @min(reason.len, reply.len - 1);
1979 @memcpy(reply[1..][0..n], reason[0..n]);
1980 self.replyTo(.{ .observer = i }, .upgrade_reply, reply[0 .. 1 + n]);
1981 self.dropObserver(i);
1982 }
1983
1965 fn dropPeer(self: *Server, p: Peer) void { 1984 fn dropPeer(self: *Server, p: Peer) void {
1966 switch (p) { 1985 switch (p) {
1967 .client => |i| self.dropClient(i), 1986 .client => |i| self.dropClient(i),
@@ -2540,17 +2559,13 @@ pub const Server = struct {
2540 // nonzero rather than hanging on a silent socket. 2559 // nonzero rather than hanging on a silent socket.
2541 // Checked BEFORE the name resolves so a client that 2560 // Checked BEFORE the name resolves so a client that
2542 // cannot be seated never spawns a shell either. 2561 // cannot be seated never spawns a shell either.
2543 proto.writeFrameBounded(fd, .exit_status, &.{1}, proto.reply_budget_ms) catch {}; 2562 return self.refuseObserver(i);
2544 self.dropObserver(i);
2545 return;
2546 }; 2563 };
2547 // Attach-or-create; null is the refusal (bad name, session 2564 // Attach-or-create; null is the refusal (bad name, session
2548 // table full, or an unknown name with no size to create 2565 // table full, or an unknown name with no size to create
2549 // at), answered exactly like the full-table no above. 2566 // at), answered exactly like the full-table no above.
2550 const si = self.sessions.resolve(self, sz.name, sz.cols, sz.rows) orelse { 2567 const si = self.sessions.resolve(self, sz.name, sz.cols, sz.rows) orelse {
2551 proto.writeFrameBounded(fd, .exit_status, &.{1}, proto.reply_budget_ms) catch {}; 2568 return self.refuseObserver(i);
2552 self.dropObserver(i);
2553 return;
2554 }; 2569 };
2555 // Past both refusals above, so the counter only ever grows 2570 // Past both refusals above, so the counter only ever grows
2556 // for an attach that seated somebody. See Stats.attaches. 2571 // for an attach that seated somebody. See Stats.attaches.
@@ -2591,44 +2606,26 @@ pub const Server = struct {
2591 // the frame handler returns cleanly and the close-all + execve 2606 // the frame handler returns cleanly and the close-all + execve
2592 // happen outside the observer's read cycle. 2607 // happen outside the observer's read cycle.
2593 .upgrade_req => { 2608 .upgrade_req => {
2594 const req = proto.parseUpgradeReq(frame.payload) catch { 2609 const req = proto.parseUpgradeReq(frame.payload) catch return self.refuseUpgrade(i, "bad frame");
2595 proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'b', 'a', 'd', ' ', 'f', 'r', 'a', 'm', 'e' }, proto.reply_budget_ms) catch self.dropObserver(i);
2596 self.dropObserver(i);
2597 return;
2598 };
2599 if (self.validateUpgrade(req, self.version)) |reason| { 2610 if (self.validateUpgrade(req, self.version)) |reason| {
2600 var reply: [256]u8 = undefined; 2611 defer self.alloc.free(reason);
2601 reply[0] = 1; 2612 return self.refuseUpgrade(i, reason);
2602 const rlen = @min(reason.len, reply.len - 1);
2603 @memcpy(reply[1..][0..rlen], reason[0..rlen]);
2604 proto.writeFrameBounded(fd, .upgrade_reply, reply[0 .. 1 + rlen], proto.reply_budget_ms) catch self.dropObserver(i);
2605 self.alloc.free(reason);
2606 self.dropObserver(i);
2607 return;
2608 } 2613 }
2609 // Accepted: write the manifest to a memfd (no CLOEXEC — the 2614 // Accepted: write the manifest to a memfd (no CLOEXEC — the
2610 // new binary must inherit it), reply, and arm the exec. 2615 // new binary must inherit it), reply, and arm the exec.
2611 const memfd = std.posix.memfd_create("mux-upgrade", 0) catch { 2616 const memfd = std.posix.memfd_create("mux-upgrade", 0) catch return self.refuseUpgrade(i, "memfd");
2612 proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'm', 'e', 'm', 'f', 'd' }, proto.reply_budget_ms) catch self.dropObserver(i);
2613 self.dropObserver(i);
2614 return;
2615 };
2616 self.writeManifestTo(memfd, self.version) catch { 2617 self.writeManifestTo(memfd, self.version) catch {
2617 std.posix.close(memfd); 2618 std.posix.close(memfd);
2618 proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'm', 'a', 'n', 'i', 'f', 'e', 's', 't' }, proto.reply_budget_ms) catch self.dropObserver(i); 2619 return self.refuseUpgrade(i, "manifest");
2619 self.dropObserver(i);
2620 return;
2621 }; 2620 };
2622 // Owned, because `req.path` points into the frame payload 2621 // Owned, because `req.path` points into the frame payload
2623 // this handler's caller frees on return, and the exec runs a 2622 // this handler's caller frees on return, and the exec runs a
2624 // pump later. execUpgrade frees it if the exec fails. 2623 // pump later. execUpgrade frees it if the exec fails.
2625 const path = self.alloc.dupe(u8, req.path) catch { 2624 const path = self.alloc.dupe(u8, req.path) catch {
2626 std.posix.close(memfd); 2625 std.posix.close(memfd);
2627 proto.writeFrameBounded(fd, .upgrade_reply, &.{ 1, 'o', 'o', 'm' }, proto.reply_budget_ms) catch self.dropObserver(i); 2626 return self.refuseUpgrade(i, "oom");
2628 self.dropObserver(i);
2629 return;
2630 }; 2627 };
2631 proto.writeFrameBounded(fd, .upgrade_reply, &.{0}, proto.reply_budget_ms) catch self.dropObserver(i); 2628 self.replyTo(.{ .observer = i }, .upgrade_reply, &.{0});
2632 self.dropObserver(i); 2629 self.dropObserver(i);
2633 self.pending_upgrade = .{ .path = path, .memfd = memfd }; 2630 self.pending_upgrade = .{ .path = path, .memfd = memfd };
2634 }, 2631 },
@@ -2651,9 +2648,7 @@ pub const Server = struct {
2651 // refusal above: the byte must be gone before the fd is, 2648 // refusal above: the byte must be gone before the fd is,
2652 // but not at the price of the whole pump. 2649 // but not at the price of the whole pump.
2653 std.debug.print("mux d: status_req for unknown session: {s}\n", .{name}); 2650 std.debug.print("mux d: status_req for unknown session: {s}\n", .{name});
2654 proto.writeFrameBounded(fd, .exit_status, &.{1}, proto.reply_budget_ms) catch {}; 2651 return self.refuseObserver(i);
2655 self.dropObserver(i);
2656 return;
2657 }; 2652 };
2658 const payload = proto.encodeStatusReply(self.buildStatusReply(si)); 2653 const payload = proto.encodeStatusReply(self.buildStatusReply(si));
2659 proto.writeFrameBounded(fd, .status_reply, &payload, proto.reply_budget_ms) catch self.dropObserver(i); 2654 proto.writeFrameBounded(fd, .status_reply, &payload, proto.reply_budget_ms) catch self.dropObserver(i);