summaryrefslogtreecommitdiff
path: root/src/macos.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/macos.zig')
-rw-r--r--src/macos.zig50
1 files changed, 48 insertions, 2 deletions
diff --git a/src/macos.zig b/src/macos.zig
index 6cd7838f..b472e6cc 100644
--- a/src/macos.zig
+++ b/src/macos.zig
@@ -125,8 +125,30 @@ const Pty = struct {
pid: posix.pid_t,
gen: u32,
reader: std.Io.Future(anyerror!void),
+ /// A command pane's child, watched to its exit (host_io.watchExit);
+ /// the pty stays open until both its end of file and its exit.
+ cmd: host_io.CommandWatch = .{},
};
+/// A watched child exited: wake the host, which takes it (`takeExits`).
+/// Called on a watcher thread holding host_io's exit lock, which
+/// pardes_deinit takes to clear the wake: safe only because the runtime's
+/// wakeup never waits for the main thread (AppDelegate's is a
+/// DispatchQueue.main.async); one that did would deadlock the quit.
+fn wakeForExit(ctx: ?*anyopaque) void {
+ const st: *State = @ptrCast(@alignCast(ctx orelse return));
+ wake(st);
+}
+
+/// The command panes' exits, told once their output is in (host_io).
+fn takeExits(st: *State) void {
+ host_io.takeExits(st.core, &st.ptys, st, closeWatched);
+}
+
+fn closeWatched(st: *State, id: usize) void {
+ reap(st, @intCast(id));
+}
+
const WatchedFile = struct {
path: []u8,
serial: u32,
@@ -782,6 +804,7 @@ fn initCore(runtime: ?*const Runtime, cols_arg: u16, rows_arg: u16) !void {
for (&st.ptys, 0..) |*slot, id| if (slot.*) |*pt| startReader(st, pt, @intCast(id));
pardes.lsp.setStatusSink(st, lspStatusSink);
+ host_io.setExitWake(st, wakeForExit);
// From here the core is touched only inside the entry points above,
// each of which takes the turn for its own duration; between them the
// 9P connections have it.
@@ -801,6 +824,7 @@ export fn pardes_deinit() void {
st.ninep = null;
}
pardes.lsp.setStatusSink(null, null);
+ host_io.setExitWake(null, null);
if (st.lsp_task) |*t| {
t.future.cancel(st.io) catch {};
st.lsp_task = null;
@@ -1268,6 +1292,8 @@ export fn pardes_watch_changed(pane: u8, generation: u32) void {
fn drainInbox(st: *State) bool {
var batch = st.inbox.take(st.io);
+ // After this batch's output, which an exit is told behind.
+ defer takeExits(st);
var did = batch.len > 0;
for (batch.slice()) |msg| {
defer msg.free(st.gpa);
@@ -1279,6 +1305,24 @@ fn drainInbox(st: *State) bool {
},
.eof => |e| {
if (st.gens[e.pane] != e.gen) continue;
+ if (st.ptys[e.pane]) |*pt| {
+ // 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) {
+ host_io.commandEof(st.core, &st.ptys, e.pane, st, closeWatched);
+ continue;
+ }
+ // Unwatched, a command's exit is read here, as its end.
+ if (st.core.panes[e.pane]) |pane| if (pane.command != null) {
+ const status = host_io.exitStatus(pt.pid, 100);
+ if (status != null) pt.pid = 0; // reaped: no shell to retire
+ reap(st, e.pane);
+ st.core.update(.{ .exited = .{ .pane = e.pane, .status = status } });
+ st.core.update(.{ .eof = .{ .pane = e.pane } });
+ continue;
+ };
+ }
reap(st, e.pane);
st.core.update(.{ .eof = .{ .pane = e.pane } });
},
@@ -1344,7 +1388,7 @@ fn restoreCore(st: *State) bool {
return false;
};
defer st.gpa.free(bytes);
- const replacement = dump.restore(st.core, bytes) catch |err| {
+ const replacement = dump.restore(st.core, bytes, path) catch |err| {
st.core.reportError(st.core.active, "Restore", err);
return false;
};
@@ -2403,6 +2447,7 @@ fn spawnShell(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
.pid = child.pid,
.gen = gen,
.reader = .{ .any_future = null, .result = {} },
+ .cmd = .{ .watched = (if (core.panes[pane]) |pn| pn.command != null else false) and host_io.watchExit(child.pid), .fd = child.file.handle },
};
var lbuf: [1024]u8 = undefined;
if (host_io.shellCwd(child.pid, &lbuf)) |wd| core.setCwd(pane, wd);
@@ -2423,7 +2468,8 @@ fn ptyResize(ctx: ?*anyopaque, pane: u8, cols: u16, rows: u16) void {
fn ptySignal(ctx: ?*anyopaque, pane: u8, sig: pardes.PtySignal) void {
const st = hostState(ctx);
- if (st.ptys[pane]) |pt| host_io.signalTty(pt.pid, pt.file.handle, sig);
+ // A command whose exit is recorded is reaped: its group may be another's.
+ if (st.ptys[pane]) |pt| if (!pt.cmd.exited) host_io.signalTty(pt.pid, pt.file.handle, sig);
}
fn ttyTaken(ctx: ?*anyopaque, pane: u8) bool {