From acd9e032a147f4aa7ad49110dc2f4fb945e7a9bb Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 27 Sep 2026 20:14:04 -0300 Subject: Keep a pane's filesystem state on the Pane instead of a slot-indexed table addr, limit, the event queue and its readers, pty/data's queue, a waiting run and the /log announcement flag lived in p.fs.panes[slot], reset when the pane went; a reused slot is how the bug class came in. acme keeps the same state on its Window (editors/acme/dat.h:226-266), so now Pane.fs holds it: announced, retired (noteRetire, which still answers a waiting run and drops the listener count first) and freed with the pane itself. Co-Authored-By: Claude Opus 5.5 --- src/ninep/pty.zig | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'src/ninep/pty.zig') diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig index 2a38f1f0..ddb7bcd1 100644 --- a/src/ninep/pty.zig +++ b/src/ninep/pty.zig @@ -176,7 +176,7 @@ pub fn writeRun(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { if (std.mem.trim(u8, line, " \t").len == 0) return tree.failText(req.tag, E.INVAL, e_bad_line); if (line.len == 0) return tree.failText(req.tag, E.INVAL, e_bad_line); for (line) |c| if (c < ' ' and c != '\t') return tree.failText(req.tag, E.INVAL, e_bad_line); - const pf = &p.fs.panes[id]; + const pf = &pane.fs; const state = pane.terminal orelse return tree.failText(req.tag, E.INVAL, e_bad_line); const marks = &state.stream.handler; if (pf.unmarked) { @@ -230,8 +230,8 @@ pub fn releaseRun(p: *Pardes, req: Req) Reply { const slot = runOf(p, req) orelse return .{ .tag = req.tag }; const idx: u8 = @intCast(slot - &p.fs.runs[0]); // The command runs on in the shell; nobody is waiting for it any more. - for (&p.fs.panes) |*pf| if (pf.run == idx) { - pf.run = null; + for (p.panes) |live| if (live) |pane| if (pane.fs.run == idx) { + pane.fs.run = null; }; slot.reset(p.gpa, 0); return .{ .tag = req.tag }; @@ -244,7 +244,7 @@ pub fn releaseRun(p: *Pardes, req: Req) Reply { pub fn noteMarks(p: *Pardes, id: usize, pane: *Pane) void { if (comptime !pardes.panes.Terminal.enabled) return; const state = pane.terminal orelse return; - const pf = &p.fs.panes[id]; + const pf = &pane.fs; const slot = &p.fs.runs[pf.run orelse return]; const marks = &state.stream.handler; if (marks.phase != .input) return; @@ -323,11 +323,10 @@ pub fn noteMarks(p: *Pardes, id: usize, pane: *Pane) void { /// The pane closed or its shell was replaced: the command's end will never /// be reported, so the run says so instead of waiting forever. -pub fn shellGone(p: *Pardes, id: usize) void { - const pf = &p.fs.panes[id]; - const idx = pf.run orelse return; +pub fn shellGone(p: *Pardes, pane: *Pane) void { + const idx = pane.fs.run orelse return; answer(p, &p.fs.runs[idx], "error shell gone", .{}); - pf.run = null; + pane.fs.run = null; } const e_bad_line = "bad command line"; @@ -388,7 +387,7 @@ test "a pane that is not a terminal has no pty/ at all" { .op = .open, .node = Node.of(serial, .pty_data), }).errno()); - try testing.expectEqual(@as(u16, 0), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers); var buf: [32]th.Dirent = undefined; const files = th.dirents(rdir(p, dir, 0).bytes, &buf); @@ -574,18 +573,18 @@ test "pty/data writes at the shell and reads the raw stream" { p.update(.{ .output = .{ .pane = 0, .bytes = "unwatched" } }); while (p.nextEffect()) |_| {} - try testing.expectEqual(@as(usize, 0), p.fs.panes[0].pty_out.buf.items.len); + try testing.expectEqual(@as(usize, 0), p.panes[0].?.fs.pty_out.buf.items.len); try testing.expectEqual(Status.again, rd(p, data, 0, 64).reply.status); // An open that only writes is not a reader: nothing queues for it. const writer = call(p, .{ .tag = 4, .op = .open, .node = data, .omode = 1 }); - try testing.expectEqual(@as(u16, 0), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers); _ = call(p, .{ .tag = 4, .op = .release, .node = data, .handle = writer.reply.handle }); const reader = call(p, .{ .tag = 5, .op = .open, .node = data }); - try testing.expectEqual(@as(u16, 1), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 1), p.panes[0].?.fs.pty_readers); try testing.expectEqual(@as(u16, 0), p.fs.listeners); - try testing.expect(!p.fs.scripted(0)); + try testing.expect(p.panes[0].?.fs.readers == 0); p.update(.{ .output = .{ .pane = 0, .bytes = "hello" } }); while (p.nextEffect()) |_| {} @@ -603,8 +602,8 @@ test "pty/data writes at the shell and reads the raw stream" { p.update(.{ .output = .{ .pane = 0, .bytes = "orphan" } }); while (p.nextEffect()) |_| {} _ = call(p, .{ .tag = 6, .op = .release, .node = data, .handle = reader.reply.handle }); - try testing.expectEqual(@as(u16, 0), p.fs.panes[0].pty_readers); - try testing.expectEqual(@as(usize, 0), p.fs.panes[0].pty_out.buf.capacity); + try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers); + try testing.expectEqual(@as(usize, 0), p.panes[0].?.fs.pty_out.buf.capacity); try testing.expectEqual(Status.again, rd(p, data, 0, 64).reply.status); // A second reader would take half the stream from the first: refused. @@ -612,7 +611,7 @@ test "pty/data writes at the shell and reads the raw stream" { const second = call(p, .{ .tag = 8, .op = .open, .node = data, .omode = 2 }); try testing.expectEqual(Status.err, second.reply.status); try testing.expectEqualStrings(tree.e_in_use, second.reply.ename); - try testing.expectEqual(@as(u16, 1), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 1), p.panes[0].?.fs.pty_readers); p.update(.{ .output = .{ .pane = 0, .bytes = "still" } }); while (p.nextEffect()) |_| {} try testing.expectEqualStrings("still", rd(p, data, 0, 64).bytes); @@ -732,7 +731,7 @@ test "the pty queue drops the oldest at its cap" { const rest: [4096]u8 = @splat('B'); events.notePtyOutput(p, 0, &oldest); for (0..events.queue_cap / rest.len + 4) |_| events.notePtyOutput(p, 0, &rest); - const q = &p.fs.panes[0].pty_out; + const q = &p.panes[0].?.fs.pty_out; try testing.expect(q.buf.items.len - q.head <= events.queue_cap); var seen: usize = 0; -- cgit v1.3