From 864407b240b812743d6f57a56daf9706b36fc027 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 15:14:00 -0300 Subject: pardes --wait for a new name waits on the pane it made, by serial, and a pane closed before it looks is done, not a failure fs.py's `pardes --wait 'wait new.txt'` failed two runs in three on the 0.0.8 merge. The stress case repeats the test's steps: 4 of 50 failed under load, each with "pardes: --wait: no pane shows that file". A new name's launch makes its pane, names it, then found it again by path in /index. The script had already seen the name, saved and removed the pane by then, so the lookup missed and --wait exited 1. Now the launch hands --wait the serial pane/new gave it. After a look, finding no pane on the file means that pane is already closed, which is the end --wait waits for: exit 0. The follow with its /index recheck covers a close in between, as before. Under the same load the stress case went 0 of 100. The same load showed a test race too, at fs.py's second Restore. The exec write is answered before the hang-up, but the client's clunk after it can meet the connection already cut. The test now requires the answer and tolerates the reset. Co-Authored-By: Claude Opus 5.5 --- src/main.zig | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/main.zig b/src/main.zig index 576efc8e..859e6fb3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -102,13 +102,18 @@ const help_text = /// pane `path` landed in (the one it already had, if open), then /log /// followed on one connection until that pane's `del` -- exit 0 -- or the /// connection ends with the session -- exit 1. -fn waitForDel(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, path: []const u8) noreturn { - const first = ninep_io.Client.read(gpa, dial, "/index", "/index") catch std.process.exit(1); - const serial = paneShowing(first, path) orelse { - std.Io.File.stderr().writeStreamingAll(io, "pardes: --wait: no pane shows that file\n") catch {}; - std.process.exit(1); +fn waitForDel(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, path: []const u8, known: ?u32) noreturn { + _ = io; + // The pane this launch made (a name not there yet) is known by its + // serial; one a look opened or went to is found by its file. Found by + // none after the look went there, it was closed already: that is the + // end being waited for, not a failure (a pane closed between the look + // and this read made `--wait` exit 1 before). + const serial = known orelse found: { + const first = ninep_io.Client.read(gpa, dial, "/index", "/index") catch std.process.exit(1); + defer gpa.free(first); + break :found paneShowing(first, path) orelse std.process.exit(0); }; - gpa.free(first); const Wait = struct { gpa: std.mem.Allocator, dial: []const u8, @@ -436,6 +441,7 @@ fn nativeMain(init: std.process.Init) !void { }; break :named std.fmt.bufPrint(&newbuf, "{s}/{s}", .{ std.mem.trimEnd(u8, dir.path, "/"), base }) catch |err| Refuse.with(init.io, word, err); }; + var made_serial: ?u32 = null; if (found_path != null) { var command_buf: [8192]u8 = undefined; const command = std.fmt.bufPrint(&command_buf, "{s}{s}\n", .{ path, word[target.path.len..] }) catch |err| Refuse.with(init.io, word, err); @@ -443,6 +449,7 @@ fn nativeMain(init: std.process.Init) !void { } else { const made = ninep_io.Client.read(arena, parent.dial, "/pane/new", "/pane/new") catch |err| Refuse.with(init.io, word, err); const serial = std.fmt.parseInt(u32, std.mem.trim(u8, made, " \n"), 10) catch |err| Refuse.with(init.io, word, err); + made_serial = serial; var name_buf: [64]u8 = undefined; const name = try std.fmt.bufPrint(&name_buf, "/pane/{d}/name", .{serial}); var line_buf: [4200]u8 = undefined; @@ -466,7 +473,7 @@ fn nativeMain(init: std.process.Init) !void { Refuse.with(init.io, word, err); }; } - if (wait) waitForDel(init.io, arena, parent.dial, path); + if (wait) waitForDel(init.io, arena, parent.dial, path, made_serial); return; } if (positional) |a| { -- cgit v1.3