diff options
Diffstat (limited to 'src/host_io.zig')
| -rw-r--r-- | src/host_io.zig | 47 |
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; |
