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.zig88
1 files changed, 67 insertions, 21 deletions
diff --git a/src/host_io.zig b/src/host_io.zig
index 68d35a9c..340513df 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -1426,12 +1426,6 @@ fn watchChild(pid: libc.pid_t) void {
_ = libc.nanosleep(&ts, null);
}
wakeForExit();
- // Once more a little later: a job the command left holding its pty
- // may keep its end of file from ever coming, and the host tells the
- // exit then (`CommandWatch.due`).
- const ts: libc.timespec = .{ .sec = 0, .nsec = (CommandWatch.grace_ms + 10) * std.time.ns_per_ms };
- _ = libc.nanosleep(&ts, null);
- wakeForExit();
}
fn wakeForExit() void {
@@ -1440,33 +1434,85 @@ fn wakeForExit() void {
if (exit_wake) |wake| wake.f(wake.ctx);
}
-/// Where a host's command pane is between its child's exit and its pty's
-/// end of file. The exit is told once the output before it has been read,
-/// at end of file -- the exit is often seen first, with output still in
-/// the pty -- or after `grace_ms` without one, a job the command left in
-/// the background holding the pty.
+/// Where a host's command pane is between its child's exit and the end of
+/// its output. The exit is told once the output before it is in: at the
+/// pty's end of file; or, when something else still holds the pty (a job
+/// the command left in the background), once nothing is waiting to be read
+/// -- checked when the exit is seen and again after each chunk of output,
+/// with no timer to miss. acme's waitthread tells an exit when it is
+/// reaped (acme.c:587-700); a terminal also has output in flight.
pub const CommandWatch = struct {
- pub const grace_ms = 50;
watched: bool = false,
+ /// The master, to ask whether output is still waiting.
+ fd: c_int = -1,
eof: bool = false,
exited: bool = false,
told: bool = false,
status: ?u8 = null,
- at: i64 = 0,
-
- /// The child is reaped, with this status.
- pub fn exit(w: *CommandWatch, status: ?u8) void {
- w.exited = true;
- w.status = status;
- w.at = nowMs();
- }
/// Whether the exit is to be told now.
pub fn due(w: *const CommandWatch) bool {
- return w.exited and !w.told and (w.eof or nowMs() - w.at >= grace_ms);
+ if (!w.exited or w.told) return false;
+ if (w.eof) return true;
+ var pfd = [1]libc.pollfd{.{ .fd = w.fd, .events = libc.POLL.IN, .revents = 0 }};
+ if (libc.poll(&pfd, 1, 0) < 0) return true;
+ // Every holder of its terminal gone: the end of file comes, after
+ // the rest of the output.
+ if (pfd[0].revents & libc.POLL.HUP != 0) return false;
+ return pfd[0].revents & libc.POLL.IN == 0;
}
};
+/// A host's pty slot, whichever way it keeps it: an optional, or one whose
+/// `fd` is -1 when empty.
+fn SlotPty(comptime S: type) type {
+ return switch (@typeInfo(S)) {
+ .optional => |o| *o.child,
+ else => *S,
+ };
+}
+
+fn slotPty(slot: anytype) ?SlotPty(@TypeOf(slot.*)) {
+ return switch (@typeInfo(@TypeOf(slot.*))) {
+ .optional => if (slot.*) |*pt| pt else null,
+ else => if (slot.fd < 0) null else slot,
+ };
+}
+
+/// Each watched child that exited: reaped, and the core told once its
+/// output is in, its pty closed (`close(ctx, id)`) if its end of file came
+/// first. Every host runs this after an exit's wake and after each chunk
+/// of a command's output; `ptys` is its slot array, each slot with a `pid`
+/// and a `cmd`.
+pub fn takeExits(core: *pardes.Pardes, ptys: anytype, ctx: anytype, comptime close: anytype) void {
+ while (takeExited()) |pid| for (ptys) |*slot| {
+ const pt = slotPty(slot) orelse continue;
+ if (!pt.cmd.watched or pt.pid != pid or pt.cmd.exited) continue;
+ pt.cmd.status = (reapExited(pid) orelse break).status;
+ pt.cmd.exited = true;
+ pt.pid = 0; // reaped: no signal or retire may reach whoever gets it next
+ break;
+ };
+ for (ptys, 0..) |*slot, id| {
+ const pt = slotPty(slot) orelse continue;
+ if (!pt.cmd.watched or !pt.cmd.due()) continue;
+ pt.cmd.told = true;
+ const eof = pt.cmd.eof;
+ core.update(.{ .exited = .{ .pane = @intCast(id), .status = pt.cmd.status } });
+ if (eof) close(ctx, id);
+ }
+}
+
+/// A watched command's pty reached end of file: the core hears its output
+/// is over; the pty is closed now if its exit was told, else when it is.
+pub fn commandEof(core: *pardes.Pardes, ptys: anytype, id: usize, ctx: anytype, comptime close: anytype) void {
+ const pt = slotPty(&ptys[id]) orelse return;
+ pt.cmd.eof = true;
+ core.update(.{ .eof = .{ .pane = @intCast(id) } });
+ if (pt.cmd.told) return close(ctx, id);
+ takeExits(core, ptys, ctx, close);
+}
+
/// Reaps `pid` if it has exited: its status (null: unknown, or not ours
/// to read), or null when it is still running -- a pid a watcher reported
/// may since have been reaped by another and taken by a new child.