summaryrefslogtreecommitdiff
path: root/src/host_io.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/host_io.zig')
-rw-r--r--src/host_io.zig47
1 files changed, 47 insertions, 0 deletions
diff --git a/src/host_io.zig b/src/host_io.zig
index 5d9dd413..81d784c5 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -1330,6 +1330,35 @@ const darwin = struct {
}
};
+/// A terminal pane's shell is done with (its pane gone, its pty closed or at
+/// end of file): say hangup, for a shell that ignores the tty's, and see it
+/// reaped, killed if it is still there 100 ms on. A thread does the waiting
+/// so that no host loop needs a timer for it (macOS's has none); a thread
+/// that cannot start kills and reaps it here.
+/// ponytail: a shell still inside its 100 ms when the editor exits is left
+/// to init; the gui and detached hosts keep their own retired lists, which
+/// this could replace.
+pub fn retireShell(pid: libc.pid_t) void {
+ if (pid <= 0) return;
+ _ = libc.kill(pid, libc.SIG.HUP);
+ if (libc.waitpid(pid, null, libc.W.NOHANG) != 0) return;
+ const thread = std.Thread.spawn(.{}, reapShell, .{ pid, 100 }) catch return reapShell(pid, 0);
+ thread.detach();
+}
+
+fn reapShell(pid: libc.pid_t, grace_ms: u32) void {
+ var waited: u32 = 0;
+ while (waited < grace_ms) : (waited += 5) {
+ // Not 0 is reaped, or someone else reaped it: either way not ours.
+ if (libc.waitpid(pid, null, libc.W.NOHANG) != 0) return;
+ const ts: libc.timespec = .{ .sec = 0, .nsec = 5 * std.time.ns_per_ms };
+ _ = libc.nanosleep(&ts, null);
+ }
+ if (libc.waitpid(pid, null, libc.W.NOHANG) != 0) return;
+ _ = libc.kill(pid, libc.SIG.KILL);
+ while (libc.waitpid(pid, null, 0) < 0 and libc.errno(-1) == .INTR) {}
+}
+
pub fn signalTty(shell_pid: libc.pid_t, master_fd: c_int, which: pardes.PtySignal) void {
const sig = switch (which) {
.int => libc.SIG.INT,
@@ -1665,6 +1694,24 @@ test "Kill's signal stops the foreground job and never the shell" {
try std.testing.expectEqual(@as(libc.pid_t, 0), libc.waitpid(sh.pid, null, libc.W.NOHANG));
}
+test "a retired shell that ignores the hangup is killed and reaped, not left a zombie" {
+ const pid = libc.fork();
+ if (pid == 0) {
+ const argv = [_:null]?[*:0]const u8{ "/bin/sh", "-c", "trap '' HUP; exec sleep 30" };
+ const envp = [_:null]?[*:0]const u8{};
+ _ = libc.execve("/bin/sh", &argv, &envp);
+ libc._exit(127);
+ }
+ try std.testing.expect(pid > 0);
+ sleepMs(100); // past the trap
+ retireShell(pid);
+ // A zombie still answers kill(pid, 0); only a reaped pid is gone.
+ var waited: i64 = 0;
+ while (libc.kill(pid, @enumFromInt(0)) == 0 and waited < 2000) : (waited += 10) sleepMs(10);
+ try std.testing.expect(libc.kill(pid, @enumFromInt(0)) != 0);
+ try std.testing.expect(waited >= 90); // it did ignore the hangup
+}
+
test "a background job is not the tty's owner" {
if (comptime !tty_probe_platform) return error.SkipZigTest;
var sh = TestShell.start() orelse return error.SkipZigTest;