diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 12:11:23 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | a8248e91873f040eed1fd24d5af7c0ca285483ef (patch) | |
| tree | c958a35a41a1b95f6b1df27b83e0b026f816b7fa /src/ninep | |
| parent | 8b18331f5e3f6c346b992019163ec3f5b6e6b721 (diff) | |
| download | pardes-a8248e91873f040eed1fd24d5af7c0ca285483ef.tar.gz pardes-a8248e91873f040eed1fd24d5af7c0ca285483ef.zip | |
A repeated err record in the log is the newest record counted, not another line
A client retrying a write that fails the same way pushed one err line after
another until the log's ring held nothing else. The same err as the newest
record is now that record with a count, (x4), as +Messages counts repeats; a
follower that has already read the record still gets each repeat as its own
line, since it may be waiting on exactly that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/events.zig | 50 |
1 files changed, 49 insertions, 1 deletions
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" { |
