summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/exec.zig23
-rw-r--r--src/ninep/ctl.zig30
-rw-r--r--src/ninep/tree.zig5
-rw-r--r--src/pardes.zig10
4 files changed, 59 insertions, 9 deletions
diff --git a/src/exec.zig b/src/exec.zig
index ca48bed1..080d0aea 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -413,9 +413,28 @@ fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane {
nt.v9fs_on_spawn = v9fs;
nt.body.mode = .tty;
placeNew(p, from, from, free, .shell);
+ if (refusedPlacement(p, free)) {
+ p.reportError(from, "Tty", error.NoPaneRoom);
+ return null;
+ }
return nt;
}
+/// A pane placeNew found no room for: taken back now, before anything is
+/// logged of it or answered with its serial (it was never announced), so
+/// no `new`/`del` pair and no serial a script was told of is left behind.
+fn refusedPlacement(p: *Pardes, id: usize) bool {
+ if (!p.unplaced.isSet(id)) return false;
+ p.unplaced.unset(id);
+ p.removePane(id, null) catch {};
+ // As sync does for a refused pane: none it squeezed stays under its
+ // minimum.
+ for (0..p.ncol) |c| if (!layout.columnAtMinimums(p, c)) {
+ _ = layout.shareColumn(p, c);
+ };
+ return true;
+}
+
/// Whether a shell would be started in `dir` and `dir` is not there: asked
/// only of a host that starts shells, and only of a local path.
pub fn missingDir(p: *Pardes, dir: []const u8) bool {
@@ -811,6 +830,10 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize {
pane.command_from = winid(p, src);
echoCommand(p, pane, line);
placeNew(p, from, from, free, .command);
+ if (refusedPlacement(p, free)) {
+ p.reportError(from, "exec", error.NoPaneRoom);
+ return null;
+ }
noteRun(p, pane, "run", line);
return free;
}
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 40ffebc8..16fbabb4 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -4379,3 +4379,33 @@ test "a Mount dial with a network it does not know is a bad dial address, EINVAL
try testing.expectEqual(E.INVAL, r.errno());
try testing.expectStringStartsWith(r.reply.ename, "Mount: bad dial address: nowhere!x");
}
+
+test "a command or a terminal refused for room is refused before anything is logged of it, and its serial is never handed out again" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const exec = Node.of(serialOf(p), .exec);
+ var buf: [32]u8 = undefined;
+ for ([_][]const u8{ "ls\n", "Tty\n" }) |line| {
+ // Each a pane of its own until the column has no room for one more.
+ var before = p.next_serial;
+ var tries: usize = 0;
+ while (tries < pardes.MAX_PANES) : (tries += 1) {
+ before = p.next_serial;
+ const r = wr(p, exec, line);
+ p.sync();
+ if (r.reply.status == .err) break;
+ } else return error.NeverRefused;
+ // Nothing said of the refused one: no new, no run, no del.
+ for (before + 1..p.next_serial + 1) |n| {
+ try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&buf, "new {d} ", .{n})));
+ try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&buf, "run {d} ", .{n})));
+ try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&buf, "del {d} ", .{n})));
+ }
+ // And the next pane made takes a serial past it.
+ const spent = p.next_serial;
+ p.update(.{ .resize = .{ .cols = 200, .rows = 200 } });
+ const made = try th.newPane(p);
+ try testing.expect(made > spent);
+ p.update(.{ .resize = .{ .cols = 80, .rows = 24 } });
+ }
+}
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index e6c139c1..40543ecf 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1905,10 +1905,11 @@ test "no placement leaves a pane shorter than its tag and two rows; a full colum
} else return error.NeverRefused;
try testing.expectEqual(E.NOSPC, refused.errno());
try testing.expect(std.mem.indexOf(u8, refused.reply.ename, "no space for a pane in that column") != null);
- // The refused pane was never there: no serial spent on it.
+ // The refused pane was never there; its serial is not handed out again
+ // (gaps are fine: a serial once said names one pane for good).
const before = p.next_serial;
try testing.expectEqual(E.NOSPC, call(p, .{ .tag = 1, .op = .open, .node = new }).errno());
- try testing.expectEqual(before, p.next_serial);
+ try testing.expectEqual(before + 1, p.next_serial);
p.sync();
var count: usize = 0;
for (p.panes) |slot| count += @intFromBool(slot != null);
diff --git a/src/pardes.zig b/src/pardes.zig
index 0690b7eb..4e578791 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -7119,13 +7119,9 @@ pub const Pardes = struct {
var refused = false;
while (p.unplaced.findFirstSet()) |id| {
p.unplaced.unset(id);
- if (p.panes[id]) |pane| {
- // Refused, it was never there: the serial it took is the
- // next pane's, as a refused Newcol's column serial is.
- const newest = pane.serial == p.next_serial;
- p.removePane(id, null) catch {};
- if (newest and p.panes[id] == null) p.next_serial -= 1;
- }
+ // Refused, it was never there. Its serial is not handed out
+ // again: one logged or answered names this pane for good.
+ if (p.panes[id] != null) p.removePane(id, null) catch {};
refused = true;
}
// A refused pane gone, no pane it squeezed stays under its minimum.