diff options
Diffstat (limited to 'src/detached/server.zig')
| -rw-r--r-- | src/detached/server.zig | 57 |
1 files changed, 51 insertions, 6 deletions
diff --git a/src/detached/server.zig b/src/detached/server.zig index 86ba2206..202eafb3 100644 --- a/src/detached/server.zig +++ b/src/detached/server.zig @@ -70,8 +70,18 @@ const Pty = struct { pid: posix.pid_t = 0, kill_at: i64 = 0, out: std.ArrayListUnmanaged(u8) = .empty, + /// A command pane's child, watched to its exit (host_io.watchExit); + /// the pty stays open, unpolled after its end of file, until both its + /// end of file and its exit. + cmd: host_io.CommandWatch = .{}, }; +/// A watched child exited: wake the loop, which takes it (`takeExits`). +fn wakeForExit(ctx: ?*anyopaque) void { + const box: *Mailbox = @ptrCast(@alignCast(ctx.?)); + box.signal(); +} + const RetiredShell = struct { pid: posix.pid_t = 0, kill_at: i64 = 0 }; const Completion = union(enum) { @@ -188,6 +198,7 @@ pub const Session = struct { } s.mailbox.wake = pair; pardes.lsp.setStatusSink(s, lspStatus); + host_io.setExitWake(&s.mailbox, wakeForExit); } fn cancelWorkers(s: *Session) void { @@ -255,6 +266,7 @@ pub const Session = struct { pub fn deinit(s: *Session) void { if (s.mailbox.wake[0] >= 0) pardes.lsp.setStatusSink(null, null); + if (s.mailbox.wake[0] >= 0) host_io.setExitWake(null, null); s.cancelWorkers(); for (s.mailbox.wake) |fd| if (fd >= 0) { _ = libc.close(fd); @@ -471,7 +483,8 @@ pub const Session = struct { s.core.screen_w, s.ninep, ) catch |err| return s.core.reportError(pane, "shell", err); - s.ptys[pane] = .{ .fd = child.file.handle, .pid = child.pid }; + const command = if (s.core.panes[pane]) |pn| pn.command != null else false; + s.ptys[pane] = .{ .fd = child.file.handle, .pid = child.pid, .cmd = .{ .watched = command and host_io.watchExit(child.pid) } }; setNonblock(child.file.handle); var lbuf: [pardes.memory.limits.host_path_cap + 1]u8 = undefined; if (host_io.shellCwd(child.pid, &lbuf)) |wd| s.core.setCwd(pane, wd); @@ -679,12 +692,43 @@ pub const Session = struct { } fn paneEof(s: *Session, pane: u8) void { - // Reaped here, its status kept, it is no shell to retire. - const status = host_io.exitStatus(s.ptys[pane].pid, 100); - if (status != null) s.ptys[pane].pid = 0; + const pt = &s.ptys[pane]; + // A command's pty stays open until its child has exited: closing + // it would hang up one that runs on without it. + // Its exit, if it came first, is told now its output is in. + if (pt.cmd.watched) { + pt.cmd.eof = true; + s.core.update(.{ .eof = .{ .pane = pane } }); + return s.takeExits(); + } + // Unwatched, a command's exit is read here, as its end. + const unwatched = if (s.core.panes[pane]) |pn| pn.command != null else false; + var status: ?u8 = null; + if (unwatched) { + status = host_io.exitStatus(pt.pid, 100); + if (status != null) pt.pid = 0; // reaped: no shell to retire + } s.closePty(pane); s.harvest(); - s.core.update(.{ .eof = .{ .pane = pane, .status = status } }); + if (unwatched) s.core.update(.{ .exited = .{ .pane = pane, .status = status } }); + s.core.update(.{ .eof = .{ .pane = pane } }); + } + + /// Each watched child that exited: reaped, the core told, and its pty + /// closed if its end of file came first. + fn takeExits(s: *Session) void { + while (host_io.takeExited()) |pid| for (&s.ptys) |*pt| { + if (pt.fd < 0 or pt.pid != pid or pt.cmd.exited) continue; + pt.cmd.exit((host_io.reapExited(pid) orelse break).status); + pt.pid = 0; // reaped: no signal or retire may reach whoever gets it next + break; + }; + for (&s.ptys, 0..) |*pt, id| { + if (pt.fd < 0 or !pt.cmd.due()) continue; + pt.cmd.told = true; + s.core.update(.{ .exited = .{ .pane = @intCast(id), .status = pt.cmd.status } }); + if (pt.cmd.eof) s.closePty(@intCast(id)); + } } fn harvest(s: *Session) void { @@ -804,7 +848,7 @@ pub const Session = struct { n += 1; } for (&s.ptys, 0..) |*pt, pane| { - if (pt.fd < 0) continue; + if (pt.fd < 0 or pt.cmd.eof) continue; fds[n] = .{ .fd = pt.fd, .events = if (pt.out.items.len != 0) poll_in | poll_out else poll_in, @@ -849,6 +893,7 @@ pub const Session = struct { // tick in a detached session, and without it an animation never ends. if (timeout_ms != 0 and monotonicMs() -| now >= timeout_ms) s.core.update(.tick); _ = s.drainCompletions(true); + s.takeExits(); s.expire(monotonicMs()); s.harvest(); s.reloadWatched(); |
