summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 16:19:21 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commitd6445bb28bcb4a0d0e5f29c6308682737d253ebe (patch)
tree22e4c50aa4f58643190e2aca1176b02ecd6f6c24
parenteb47ded95f43e50dc24b841289b68814105dcfb7 (diff)
downloadpardes-d6445bb28bcb4a0d0e5f29c6308682737d253ebe.tar.gz
pardes-d6445bb28bcb4a0d0e5f29c6308682737d253ebe.zip
A failure said while a 9P write waits on it posts no msg, only the write's err
A failure the editor said as it performed a write's work (a Save's disk write, a shell, a language server) was posted as a msg and taken back out when the write's err was logged, which it cannot be once a log follower has read it: a follower saw both. Such a failure is now posted without a msg in the first place (fs.write_waits), and nothing is taken back. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/9p_io.zig8
-rw-r--r--src/Messages.zig2
-rw-r--r--src/fs.zig4
-rw-r--r--src/ninep/ctl.zig10
4 files changed, 20 insertions, 4 deletions
diff --git a/src/9p_io.zig b/src/9p_io.zig
index 7f490f81..6f05e3ab 100644
--- a/src/9p_io.zig
+++ b/src/9p_io.zig
@@ -312,11 +312,14 @@ pub const Listener = struct {
if (restoring) l.restore_writer.store(true, .release);
defer if (restoring) l.restore_writer.store(false, .release);
core.fs.late_failure_len = 0;
+ // What fails while this waits is this write's err, never a msg too.
+ core.fs.write_waits = true;
if (core.effects_len != 0) pardes.turn.awaitSettled(epoch);
if (core.fs.lsp_answer_at) |n| {
core.fs.lsp_answer_at = null;
pardes.turn.awaitLsp(n);
}
+ core.fs.write_waits = false;
// ponytail: one slot, so a failure of another client's effects that
// settle in the same wait is told to this write too.
if (l.core == core and core.fs.late_failure_len != 0) {
@@ -327,9 +330,8 @@ pub const Listener = struct {
pardes.ctlfs.E.NOENT
else if (std.mem.indexOf(u8, late, "no space") != null) pardes.ctlfs.E.NOSPC else pardes.ctlfs.E.IO;
const failed = pardes.ctlfs.failText(req.tag, errno, late);
- // Its err record says it (the path in it); the msg the failure
- // was also said as goes, as a builtin's failing a write does.
- pardes.ctlfs.events.dropMessage(core, core.fs.late_failure[0..core.fs.late_failure_len]);
+ // Its err record says it (the path in it), the one record: it
+ // was said with no msg while this waited (fs.write_waits).
pardes.ctlfs.events.noteError(core, req, failed);
return conn.reply(&failed, "");
}
diff --git a/src/Messages.zig b/src/Messages.zig
index aebaff0c..7342f726 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -505,7 +505,7 @@ pub fn reportFailure(p: *Pardes, id: usize, text: []const u8) void {
p.fs.failures +%= 1;
// A builtin a ctl write runs: its first error is also the write's, cut
// between words.
- const failing_write = p.fs.no_prompt or p.fs.capturing;
+ const failing_write = p.fs.no_prompt or p.fs.capturing or p.fs.write_waits;
if (failing_write and p.fs.failure_len == 0) {
const kept = if (text.len > p.fs.failure.len) clip(text, p.fs.failure.len - 3) else text;
@memcpy(p.fs.failure[0..kept.len], kept);
diff --git a/src/fs.zig b/src/fs.zig
index 2487ffee..edd8fcb8 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -1330,6 +1330,10 @@ pub const Namespace = struct {
/// its header), 0 when unknown: a write shorter than it is the whole of
/// what its client wrote, one that fills it may be cut and go on.
write_room: u32 = 0,
+ /// A 9P write waits on what the editor performs for it (a Save's disk
+ /// 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,
/// 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/ctl.zig b/src/ninep/ctl.zig
index 397f3339..63e7e76a 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -2234,6 +2234,16 @@ test "a buffer whose file was deleted on disk is dirty, and Del asks first" {
try testing.expect(p.paneBySerial(serial) == null);
}
+test "a failure said while a 9P write waits on it posts no msg, only the write's err" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ p.fs.write_waits = true;
+ p.saveFailed(@intCast(p.active), "/nowhere/zz.txt", error.FileNotFound);
+ p.fs.write_waits = false;
+ try testing.expect(!th.logHas(p, "msg "));
+ try testing.expect(p.fs.late_failure_len > 0);
+}
+
test "every EINVAL a write gets says why, in its err record too; DEL is a control character in a line" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();