summaryrefslogtreecommitdiff
path: root/src/ninep/events.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 18:23:34 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitd572edefe32284db343259b0b7e12c9470366b28 (patch)
treeeba0723b35fc6bcf58d6d55e712ea9a365e794ef /src/ninep/events.zig
parentafc72ac3db0840d8571dcfb8e2e182a241bdecaf (diff)
downloadpardes-d572edefe32284db343259b0b7e12c9470366b28.tar.gz
pardes-d572edefe32284db343259b0b7e12c9470366b28.zip
A followed log, event or pty/data answers a read inside its last answer from it again, so bash's read loses nothing
bash's read on a seekable fd takes a chunk, keeps one line and lseeks back to just past it; a stream had already moved on, so the rest of the chunk was lost and a while-read loop over the log dropped records. Each stream open keeps its last answer and the offset it was read at, and a read strictly inside it is answered from it; a read at the same offset, as a client that ignores offsets makes, still gets the next record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/events.zig')
-rw-r--r--src/ninep/events.zig26
1 files changed, 26 insertions, 0 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index ffdea95b..1c66c18f 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -565,6 +565,32 @@ const serialOf = th.serialOf;
const Node = tree.Node;
const Status = tree.Status;
+test "a stream read at an offset inside its last answer gets the rest of it again, as bash's read seeks back" {
+ const p = try withFile(testing.allocator, "one\n");
+ defer p.deinit();
+ p.update(.tick);
+ while (p.nextEffect()) |_| {}
+ const log = @intFromEnum(tree.TopFile.log);
+ const f = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle;
+ const frozen: u64 = call(p, .{ .tag = 2, .op = .read, .node = log, .handle = f, .size = 8192 }).bytes.len;
+ _ = call(p, .{ .tag = 3, .op = .write, .node = log, .handle = f, .data = "follow" });
+ notePath(p, "dump", "/a");
+ notePath(p, "dump", "/b");
+ const Read = struct {
+ fn at(pp: *Pardes, h: u32, off: u64) []const u8 {
+ return call(pp, .{ .tag = 4, .op = .read, .node = @intFromEnum(tree.TopFile.log), .handle = h, .off = off, .size = 128 }).bytes;
+ }
+ };
+ try testing.expectEqualStrings("dump /a\n", Read.at(p, f, frozen));
+ // bash kept "dump" and seeked back to just past it.
+ try testing.expectEqualStrings(" /a\n", Read.at(p, f, frozen + 4));
+ // At the end of what it was given: the next record.
+ try testing.expectEqualStrings("dump /b\n", Read.at(p, f, frozen + 8));
+ // A client that reads every record at one offset still gets new ones.
+ notePath(p, "dump", "/c");
+ try testing.expectEqualStrings("dump /c\n", Read.at(p, f, frozen + 8));
+}
+
test "event records are acme's bytes, one per read, and .again when empty" {
const gpa = testing.allocator;
const p = try withFile(gpa, "Msg fs-ran\n");