summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/fs.md5
-rw-r--r--src/ninep/events.zig50
2 files changed, 53 insertions, 2 deletions
diff --git a/docs/fs.md b/docs/fs.md
index ffb509cf..bd8ca055 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -335,7 +335,10 @@ includes each builtin announcing itself as it runs), and `err <serial|->
<file>: <why>` for every write or truncation the tree refused or that
failed -- through a mount a shell sees only the errno its kernel mapped the
reply to, usually `Invalid argument`, and this is the reason (`err 3 addr:
-no match for regexp`). There is no per-pane error file to read instead:
+no match for regexp`). The same err again, before any follower has read
+the first, is that record counted (`err 3 addr: no match for regexp (x4)`),
+so a client retrying a failing write does not push the rest out of the
+ring; a follower that read it gets each repeat. There is no per-pane error file to read instead:
acme's `errors` only takes text, and one record stream is simpler to watch
than a file per pane. A `msg` said while a
pane is being made can precede that pane's `new`; panes present at boot are
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index 8ee25b74..69dc271d 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -177,7 +177,42 @@ pub fn noteError(p: *Pardes, req: Req, reply: Reply) void {
};
}
var buf: [256]u8 = undefined;
- pushLog(p, std.fmt.bufPrint(&buf, "err {s} {s}: {s}\n", .{ serial, file, why[0..@min(why.len, 200)] }) catch return);
+ const record = std.fmt.bufPrint(&buf, "err {s} {s}: {s}\n", .{ serial, file, why[0..@min(why.len, 200)] }) catch return;
+ for (record[0 .. record.len - 1]) |*c| if (c.* < ' ') {
+ c.* = ' ';
+ };
+ // A client retrying a write that fails the same way would fill the ring
+ // with one line, so a repeat of the newest record is that record counted,
+ // `(x3)`, as +Messages counts its repeats; unless a follower has read it
+ // already and so waits on the repeat as a line of its own.
+ const q = &p.fs.log;
+ var at = q.head;
+ var last: ?usize = null;
+ var seq = q.dropped;
+ while (at + 4 <= q.buf.items.len) : (seq += 1) {
+ last = at;
+ at += 4 + std.mem.readInt(u32, q.buf.items[at..][0..4], .little);
+ }
+ const last_at = last orelse return pushLog(p, record);
+ const last_seq = seq - 1;
+ var said = q.buf.items[last_at + 4 ..];
+ said = said[0 .. said.len - 1];
+ var times: u32 = 1;
+ if (std.mem.endsWith(u8, said, ")")) if (std.mem.lastIndexOf(u8, said, " (x")) |x| {
+ if (std.fmt.parseInt(u32, said[x + 3 .. said.len - 1], 10)) |n| {
+ times = n;
+ said = said[0..x];
+ } else |_| {}
+ };
+ if (!std.mem.eql(u8, said, record[0 .. record.len - 1])) return pushLog(p, record);
+ for (p.fs.opens) |o| if (o.node != 0 and o.what == .log) {
+ const l = o.what.log;
+ if (l.follow and (l.next > last_seq or (l.next == last_seq and l.part > 0))) return pushLog(p, record);
+ };
+ var counted: [280]u8 = undefined;
+ const again = std.fmt.bufPrint(&counted, "{s} (x{d})\n", .{ said, times +| 1 }) catch return;
+ q.buf.shrinkRetainingCapacity(last_at);
+ pushLog(p, again);
}
/// The log is one ring that records whether or not anyone reads it. A record
@@ -819,7 +854,20 @@ test "a refused or failed write is an err record in the log, saying which file a
// A write that succeeds says nothing.
_ = wr(p, Node.of(serial, .addr), "#0");
try testing.expectEqual(Status.again, call(p, .{ .tag = 5, .op = .read, .node = log, .handle = f, .off = frozen, .size = 4096 }).reply.status);
+ // A follower that has read an err waits on its repeat: a line of its own.
+ _ = wr(p, Node.of(serial, .addr), "/nowhere/");
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "err {d} addr: no match for regexp\n", .{serial}), next.record(p, f, frozen));
+ _ = wr(p, Node.of(serial, .addr), "/nowhere/");
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "err {d} addr: no match for regexp\n", .{serial}), next.record(p, f, frozen));
_ = call(p, .{ .tag = 6, .op = .release, .node = log, .handle = f });
+
+ // With the follower gone, repeats are the newest record counted: the
+ // one it read, and three more.
+ for (0..3) |_| _ = wr(p, Node.of(serial, .addr), "/elsewhere/");
+ const g = call(p, .{ .tag = 7, .op = .open, .node = log }).reply.handle;
+ const text = call(p, .{ .tag = 8, .op = .read, .node = log, .handle = g, .size = 1 << 16 }).bytes;
+ try testing.expect(std.mem.endsWith(u8, text, try std.fmt.bufPrint(&want, "regexp\nerr {d} addr: no match for regexp (x4)\n", .{serial})));
+ _ = call(p, .{ .tag = 9, .op = .release, .node = log, .handle = g });
}
test "opens of the log share the open records, and a closed one frees its record" {