From 990c2e1b184e9cb1fddd8eb1b05a472cfb113dde Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 12:01:34 -0300 Subject: A closed terminal's shell is reaped on tty and macOS, killed if it ignores the hangup tty and macOS hung the pty up and called waitpid once without waiting, so a shell still exiting, or one that ignores SIGHUP, stayed a zombie or ran on with nobody reading it; tty also never reaped a shell that exited by itself, and its spawn into an occupied slot closed the old pty without ending the shell. host_io's retireShell says hangup, waits 100 ms on a thread (macOS has no host timer to poll from), then kills and reaps. The gui's own retired list, when full, left the slot holding the old shell and refused the next spawn into that pane; it now hands that shell to retireShell instead. Co-Authored-By: Claude Opus 5.5 --- src/pardes.zig | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index f3348ea5..59556649 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1125,6 +1125,32 @@ test "Tty spawns a raw shell in the caller's directory" { try std.testing.expect(found); } +test "a terminal respawned into a closed pane's slot hangs the old shell up first" { + // Hosts keep a shell per slot: were the spawn first, the close would + // end the new shell and leave the old one running. + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 }); + defer p.deinit(); + try std.testing.expect(p.executeBuiltinLine(0, "Tty")); + const slot = p.active; + while (p.nextEffect()) |_| {} + try p.removePane(slot, null); + try std.testing.expect(p.executeBuiltinLine(0, "Tty")); + try std.testing.expectEqual(slot, p.active); + var closed = false; + var spawned = false; + while (p.nextEffect()) |effect| switch (effect) { + .close_pty => |c| if (c.pane == slot) { + try std.testing.expect(!spawned); + closed = true; + }, + .spawn => |s| if (s.pane == slot) { + spawned = true; + }, + else => {}, + }; + try std.testing.expect(closed and spawned); +} + test "Tty9p marks only the new Linux terminal for a mounted shell" { if (comptime !hosted or @import("builtin").os.tag != .linux) return error.SkipZigTest; const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); -- cgit v1.3