summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 00:40:03 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit6732ce6edcf1562d3e126ed02f96466ab07c498e (patch)
tree77c68f4357b335e67b34f19e248f1ea00240e0eb /src/ninep/tree.zig
parentfad6bf0cc7cc2cb579d9ad03147a7014906f6b92 (diff)
downloadpardes-6732ce6edcf1562d3e126ed02f96466ab07c498e.tar.gz
pardes-6732ce6edcf1562d3e126ed02f96466ab07c498e.zip
A desktop session holds 64 panes, and every route that finds none free says so
Sixteen panes was far too few for a real session. MAX_PANES is now 64 on a desktop and stays 16 on the board; the 48 more slots cost about 3.7 KB each in fixed tables (the core 1.33 to 1.45 MB, its surface 19 to 72 KB), and a pane itself (84 KB and its terminal) is allocated only when it opens. At the cap, pane/new fails with `pane/new: no pane slots: 64 max` and an err record rather than a bare ENFILE, a 9P look or exec that would open a pane fails its write with the same words and reads back empty, and New, Tty, Tutor and Alt-n say it on the message row (their ctl writes fail with it). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/tree.zig')
-rw-r--r--src/ninep/tree.zig31
1 files changed, 30 insertions, 1 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 3be75960..c4ade1f7 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -664,7 +664,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply {
// pane it made, so reading it twice answers the same one and
// closing it leaves the pane alone.
.new => {
- const slot = p.freeSlot() orelse return Reply.fail(req.tag, E.NFILE);
+ const slot = p.freeSlot() orelse return failText(req.tag, E.NFILE, std.fmt.bufPrint(&p.fs.ename, "pane/new: " ++ pardes.Messages.no_pane_slots, .{pardes.MAX_PANES}) catch "no pane slots");
p.newScratchFor(p.active, .fs);
const made = p.panes[slot] orelse return Reply.fail(req.tag, E.NFILE);
return .{ .tag = req.tag, .handle = made.serial };
@@ -1284,3 +1284,32 @@ test "editor paths resolve to the same nodes the wire serves" {
try testing.expect(resolveSelf(p, "pane/new/body") == null);
try testing.expectEqual(before, p.next_serial);
}
+
+test "at the pane cap, pane/new, look and New each say so, and look reads back empty" {
+ const p = try th.withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const new = @intFromEnum(TopFile.new);
+ while (p.freeSlot() != null) try testing.expectEqual(Status.ok, call(p, .{ .tag = 1, .op = .open, .node = new }).reply.status);
+ var want: [64]u8 = undefined;
+ const said = try std.fmt.bufPrint(&want, "no pane slots: {d} max", .{pardes.MAX_PANES});
+ const refused = call(p, .{ .tag = 2, .op = .open, .node = new });
+ try testing.expectEqual(E.NFILE, refused.errno());
+ try testing.expect(std.mem.endsWith(u8, refused.reply.ename, said));
+ try testing.expect(th.logHas(p, said));
+ // A look that would open a pane fails its write, and says the same.
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "f.txt", .data = "f\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)];
+ var line: [4200]u8 = undefined;
+ const look_node = @intFromEnum(TopFile.look);
+ const looked = th.wr(p, look_node, try std.fmt.bufPrint(&line, "{s}/f.txt\n", .{dir}));
+ try testing.expect(looked.errno() != 0);
+ try testing.expect(std.mem.indexOf(u8, looked.reply.ename, said) != null);
+ try testing.expectEqualStrings("", th.rd(p, look_node, 0, 64).bytes);
+ // New from a pane's ctl fails as well.
+ const ctl_node = Node.of(p.panes[0].?.serial, .ctl);
+ const made = th.wr(p, ctl_node, "New\n");
+ try testing.expect(std.mem.indexOf(u8, made.reply.ename, said) != null);
+}