summaryrefslogtreecommitdiff
path: root/src/detached/server.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/detached/server.zig')
-rw-r--r--src/detached/server.zig57
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();