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