From bcd8388d74e29398cb528206b05fd9491a4d4153 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 23:10:45 -0300 Subject: An event reader whose pane closes reads EOF, not ENOENT A script following a pane's event in a loop ended in an error when the pane went, though nothing had gone wrong: the pane was simply done. A read on an event open whose pane has closed now answers no bytes, the end a pipe's reader sees when its writer goes, so cat and while-read end cleanly. pty/data keeps acme's answer. Co-Authored-By: Claude Opus 5.5 --- src/ninep/events.zig | 6 +++--- src/ninep/tree.zig | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 9d59e6cc..f5631adf 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -1010,10 +1010,10 @@ test "a pane deleted while its event file is open leaves no suppression behind" _ = wr(p, Node.of(serial, .ctl), "Del\n"); try testing.expect(p.paneBySerial(serial) == null); try testing.expectEqual(@as(u16, 0), p.fs.listeners); - // The reader's next read hears what acme says of a window gone under it. + // The reader's next read is its end: no more records, no error. const shut = call(p, .{ .tag = 19, .op = .read, .node = event, .handle = a.reply.handle, .size = 64 }); - try testing.expectEqual(E.NOENT, shut.errno()); - try testing.expectEqualStrings(tree.e_shut_down, shut.reply.ename); + try testing.expectEqual(tree.Status.ok, shut.reply.status); + try testing.expectEqualStrings("", shut.bytes); _ = call(p, .{ .tag = 20, .op = .release, .node = event, .handle = a.reply.handle }); _ = call(p, .{ .tag = 21, .op = .release, .node = event, .handle = b.reply.handle }); diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 8c536c51..c71a706f 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -1094,10 +1094,14 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply { // A run's answer outlives the pane it ran in. if (t.file == .pty_run and req.handle != 0) return pty.readRun(p, req); const id = p.paneBySerial(t.serial) orelse { - // An event or pty/data read the pane closed under: acme's - // answer to the same (editors/acme/xfid.c:1005). - if (openOf(p, req)) |o| if (o.what == .event or o.what == .pty_data) - return failText(req.tag, E.NOENT, e_shut_down); + // An event reader whose pane closed reads its end: there + // are no more records, as a pipe's writer gone is EOF, so + // `while read` and `cat` end cleanly. A pty/data read the + // pane closed under keeps acme's answer (xfid.c:1005). + if (openOf(p, req)) |o| { + if (o.what == .event) return .{ .tag = req.tag }; + if (o.what == .pty_data) return failText(req.tag, E.NOENT, e_shut_down); + } return Reply.fail(req.tag, E.NOENT); }; const pn = p.panes[id].?; -- cgit v1.3