diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 16:33:12 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 494dae826c3c567b0eea6e91936628a2952fb5c6 (patch) | |
| tree | 2c87a454c40cc78033c5b5da4bce74454727b9d8 | |
| parent | ff3738b2fe8312b0a108cf7301ec99e1c4d0a24b (diff) | |
| download | pardes-494dae826c3c567b0eea6e91936628a2952fb5c6.tar.gz pardes-494dae826c3c567b0eea6e91936628a2952fb5c6.zip | |
A repeated log record is counted only when its whole text repeats, not just what was cut to fit
(xN) compared records after msg and err cut long texts to fit, so two different long failures that agreed in their first 200-odd bytes were counted as one. The whole text of the newest counted record is now hashed, and a repeat needs it too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/fs.zig | 3 | ||||
| -rw-r--r-- | src/ninep/events.zig | 27 |
2 files changed, 25 insertions, 5 deletions
@@ -1334,6 +1334,9 @@ pub const Namespace = struct { /// write, a shell, a language server): a failure said meanwhile is that /// write's err, so it posts no msg (Messages.reportFailure). write_waits: bool = false, + /// The whole text of the newest counted log record, hashed: its record + /// may be cut, and a repeat is only the same whole text again. + log_full_hash: u64 = 0, /// Writes one open made in a row into one place of a pane's body (an /// append to `body`, an insert at `data`'s address), held and put in /// as one edit when anything else comes (ninep/pane.zig flushBatch): a diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 570cae78..f76a5212 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -171,7 +171,7 @@ pub fn noteMessage(p: *Pardes, serial: u32, said: []const u8) void { pushCounting(p, (if (serial == 0) std.fmt.bufPrint(&buf, "msg - {s}{s}\n", .{ kept, cut }) else - std.fmt.bufPrint(&buf, "msg {d} {s}{s}\n", .{ serial, kept, cut })) catch return); + std.fmt.bufPrint(&buf, "msg {d} {s}{s}\n", .{ serial, kept, cut })) catch return, said); } /// Records `err <serial|-> <file>: <why>` for a write or truncation that was @@ -186,7 +186,7 @@ pub fn noteMessage(p: *Pardes, serial: u32, said: []const u8) void { pub fn noteErrCounted(p: *Pardes, text: []const u8) void { bootFirst(p); var buf: [pardes.memory.limits.host_path_cap + 16]u8 = undefined; - pushCounting(p, std.fmt.bufPrint(&buf, "err {s}\n", .{text}) catch return); + pushCounting(p, std.fmt.bufPrint(&buf, "err {s}\n", .{text}) catch return, text); } pub fn notePath(p: *Pardes, what: []const u8, path: []const u8) void { @@ -239,7 +239,7 @@ pub fn noteError(p: *Pardes, req: Req, reply: Reply) void { for (record[0 .. record.len - 1]) |*c| if (c.* < ' ') { c.* = ' '; }; - pushCounting(p, record); + pushCounting(p, record, reason); } /// Drops the newest record when it is a msg saying `text` (of any pane) @@ -256,7 +256,11 @@ pub fn dropMessage(p: *Pardes, text: []const u8) void { /// counts that one instead: `<record> (x2)`, the count being every time it /// was said. One a follower has already read is not rewritten: the repeat /// is a new line carrying the running count, `(x3)`. -fn pushCounting(p: *Pardes, raw: []u8) void { +/// `full` is the text before any cut: two records that agree only in what +/// was cut to fit are no repeat (`fs.log_full_hash`). +fn pushCounting(p: *Pardes, raw: []u8, full: []const u8) void { + const full_hash = std.hash.Wyhash.hash(0, full); + defer p.fs.log_full_hash = full_hash; // 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 @@ -272,7 +276,7 @@ fn pushCounting(p: *Pardes, raw: []u8) void { said = said[0..x]; } else |_| {} }; - if (!std.mem.eql(u8, said, record[0 .. record.len - 1])) return pushLog(p, record); + if (!std.mem.eql(u8, said, record[0 .. record.len - 1]) or p.fs.log_full_hash != full_hash) return pushLog(p, record); var counted: [pardes.Messages.LoggedMessage.cap + 64]u8 = undefined; const again = std.fmt.bufPrint(&counted, "{s} (x{d})\n", .{ said, times +| 1 }) catch return; if (!followerRead(p, last.seq)) p.fs.log.buf.shrinkRetainingCapacity(last.at); @@ -373,6 +377,8 @@ fn shownAs(text: []const u8, out: []u8, escape_newline: bool) []u8 { } fn pushLog(p: *Pardes, raw: []u8) void { + // A record not counted: the next one is no repeat of a counted one. + p.fs.log_full_hash = 0; var clean: [4 * 4096 + 256]u8 = undefined; const record = sanitize(raw, &clean); // One record larger than the ring would push every other out and then @@ -1295,3 +1301,14 @@ test "a long err record is cut between words, with an ellipsis" { try testing.expect(std.mem.endsWith(u8, record, "abcdefgh…\n")); _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h }); } + +test "two failures that agree only in what their records cut are two records, not one counted" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const long = "why " ** 80; + p.setMessage(0, long ++ "first"); + p.setMessage(0, long ++ "second"); + try testing.expect(!th.logHas(p, "(x2)")); + p.setMessage(0, long ++ "second"); + try testing.expect(th.logHas(p, "(x2)")); +} |
