diff options
| -rw-r--r-- | docs/fs.md | 5 | ||||
| -rw-r--r-- | src/ninep/events.zig | 50 |
2 files changed, 53 insertions, 2 deletions
@@ -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" { |
