summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/exec.zig16
-rw-r--r--src/ninep/ctl.zig28
-rw-r--r--src/ninep/tree.zig3
3 files changed, 45 insertions, 2 deletions
diff --git a/src/exec.zig b/src/exec.zig
index 47484105..ca48bed1 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -766,10 +766,13 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize {
// would hang that job up.
// From a column's tag, only a finished pane in that column is reused,
// as the new one goes in that column (placeNew).
- const reuse: ?usize = if (!p.exec_header and p.exec_column == null and src != null and src.?.command != null and src.?.command_done and !src.?.command_pty) from else for (p.panes, 0..) |slot, i| {
+ // Nor one leased: an open that was answered its serial and is still
+ // open reads that pane's output, which another client's command must
+ // not replace; a new pane runs it instead.
+ const reuse: ?usize = if (!p.exec_header and p.exec_column == null and src != null and src.?.command != null and src.?.command_done and !src.?.command_pty and !leased(p, src.?.serial)) from else for (p.panes, 0..) |slot, i| {
const other = slot orelse continue;
if (p.exec_column) |c| if (if (layout.findPane(p, i)) |f| f.col != c else true) continue;
- if (other.command != null and other.command_done and !other.command_pty and std.mem.eql(u8, other.cwdSlice(), dir)) break i;
+ if (other.command != null and other.command_done and !other.command_pty and std.mem.eql(u8, other.cwdSlice(), dir) and !leased(p, other.serial)) break i;
} else null;
if (reuse) |id| {
const pane = p.panes[id].?;
@@ -812,6 +815,15 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize {
return free;
}
+/// Whether an open of a look or exec file holds `serial` from its own write
+/// (tree.Open.results): its client is reading that pane.
+fn leased(p: *const Pardes, serial: u32) bool {
+ for (p.fs.opens) |o| if (o.node != 0 and o.results.wrote) {
+ if (std.mem.indexOfScalar(u32, o.results.list[0..o.results.len], serial) != null) return true;
+ };
+ return false;
+}
+
/// The serial a command's `$winid` names: the pane it was run from, none
/// (0) from a column's or the workspace's tag.
fn winid(p: *const Pardes, src: ?*Pane) u32 {
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index bcf9ffd6..c88eb328 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -4028,3 +4028,31 @@ test "pager answers its +Pager on the open that asked, whatever another client e
// One directory a write.
try testing.expectEqual(E.INVAL, wr(p, pager, "/a\n/b\n").errno());
}
+
+test "a command pane an exec open was answered is leased to it while it stays open: another client's command gets a pane of its own" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const exec = Node.of(serialOf(p), .exec);
+ const Client = struct {
+ fn run(pp: *Pardes, node: u64, h: u32, line: []const u8) !usize {
+ try testing.expectEqual(Status.ok, call(pp, .{ .tag = 2, .op = .write, .node = node, .handle = h, .data = line }).reply.status);
+ const id = pp.paneBySerial(pp.fs.results[0]).?;
+ // Its command finishes and its output ends.
+ pp.panes[id].?.command_done = true;
+ pp.panes[id].?.command_pty = false;
+ return id;
+ }
+ };
+ const a = call(p, .{ .tag = 1, .op = .open, .node = exec, .omode = 2 }).reply.handle;
+ const b = call(p, .{ .tag = 1, .op = .open, .node = exec, .omode = 2 }).reply.handle;
+ const a_pane = try Client.run(p, exec, a, "echo a\n");
+ const b_pane = try Client.run(p, exec, b, "echo b\n");
+ try testing.expect(a_pane != b_pane);
+ // Each one's next command runs in its own pane again.
+ try testing.expectEqual(a_pane, try Client.run(p, exec, a, "echo a2\n"));
+ try testing.expectEqual(b_pane, try Client.run(p, exec, b, "echo b2\n"));
+ // Closed, a lease ends: the directory's one command pane is reused.
+ _ = call(p, .{ .tag = 3, .op = .release, .node = exec, .handle = a, .opened = true });
+ try testing.expectEqual(Status.ok, wr(p, exec, "echo c\n").reply.status);
+ try testing.expectEqual(a_pane, p.paneBySerial(p.fs.results[0]).?);
+}
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 9c3e1e9c..e5d1c848 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1212,6 +1212,9 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
o.pending.shrinkRetainingCapacity(rest);
var w = req;
w.data = whole;
+ // The command pane this open was answered holds no lease against its
+ // own next command (exec.leased): that one may run there again.
+ if (resultsFile(target)) o.results.len = 0;
var reply = writeNow(p, w, target);
if (resultsFile(target)) {
@memcpy(o.results.list[0..p.fs.results_len], p.fs.results[0..p.fs.results_len]);