summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig.zon4
-rw-r--r--docs/fs.md7
-rw-r--r--src/9p.zig8
-rw-r--r--src/9p_io.zig5
-rw-r--r--test/selfmount.py7
5 files changed, 26 insertions, 5 deletions
diff --git a/build.zig.zon b/build.zig.zon
index e80961ef..4d661759 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -9,8 +9,8 @@
// read-only HTTPS URL is what a manifest can carry. Re-pin with
// `zig fetch --save=cloud9 git+https://git.sr.ht/~gbrls/cloud9#<commit>`.
.cloud9 = .{
- .url = "git+https://git.sr.ht/~gbrls/cloud9#f35b7ed09d0a170e4ffe55b5286c28f74c9a8760",
- .hash = "cloud9-0.1.0-yt86quEKHgCwewf5XRlEZji1JCmdsfufsrZYGZqzHj5_",
+ .url = "git+https://git.sr.ht/~gbrls/cloud9#705be665742fa0332585a4321640b5e2245610cb",
+ .hash = "cloud9-0.1.0-yt86qt8NHgDuS2WNp0TgW03h-eVIgWmkznn0t0nMQKbr",
},
// Pinned to the exact libvaxis commit ghostty is built against, so it
// is compatible with ghostty's uucode configuration (the one we share
diff --git a/docs/fs.md b/docs/fs.md
index 2660bada..fe78fb19 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -179,8 +179,11 @@ files`.
**Held reads.** A read with nothing to give yet (a followed `log`, `event`,
`pty/data`, `pty/run` before its answer) waits in the editor and is answered
when news comes. A second read on that open meanwhile fails `file in use`.
-A read the client flushed is dropped. Through a FUSE mount bash's `read -t`
-cannot time out: wrap the loop in `timeout N`.
+A read the client flushed is dropped. One connection holds at most 128
+reads at once; the next is refused `too many reads waiting: 128`. A mount
+(9ns) is one connection, so that is 128 followers through it, and the mount
+keeps answering everything else beside them. Through a FUSE mount bash's
+`read -t` cannot time out: wrap the loop in `timeout N`.
**Stats.** Lengths are real (for `event` and `pty/data` the next record's,
zero when none waits; for `log` what an open would freeze). The qid
diff --git a/src/9p.zig b/src/9p.zig
index 837525cb..67b3b39a 100644
--- a/src/9p.zig
+++ b/src/9p.zig
@@ -61,7 +61,13 @@ pub const msize: u32 = 65536;
/// (src/9p_io.zig, `serve`), and a write it cannot keep would fail instead.
/// The largest write is msize less the Twrite header, 23 bytes (the test
/// below asks a client), one byte more than `iohdrsz` allows for.
-pub const editor: cloud9.fs.Options = .{ .fid_capacity = max_fids, .name_capacity = 255, .park_data_max = msize - 23 };
+/// Up to 128 reads held on one connection (a follower each of the log or
+/// an event file); the next is refused `too many reads waiting: 128`. A
+/// mount (9ns) keeps a wider window, 256, so held reads never take every
+/// one of its workers.
+pub const editor: cloud9.fs.Options = .{ .fid_capacity = max_fids, .name_capacity = 255, .park_data_max = msize - 23, .slot_capacity = max_held };
+
+pub const max_held: usize = 128;
/// The board's engine: the GPIO firmware's budget (src/esp32p4_9p.zig).
pub const board: cloud9.fs.Options = .{ .fid_capacity = board_fids, .name_capacity = board_name_capacity };
diff --git a/src/9p_io.zig b/src/9p_io.zig
index 5a68238c..0eaa2d7e 100644
--- a/src/9p_io.zig
+++ b/src/9p_io.zig
@@ -1542,6 +1542,11 @@ test "a change waits while the editor is out mid-step, a read does not, and the
try testing.expectEqualStrings("after\n", pane.file.?.content);
}
+test "a connection holds up to 128 reads, and the next is refused in words" {
+ try testing.expectEqual(@as(usize, 128), ninep.max_held);
+ if (comptime supported) try testing.expectEqualStrings("too many reads waiting: 128", Runner.Engine.e_waiting);
+}
+
test "a held read is answered when the log has news, and a flushed one spends nothing" {
if (comptime !supported) return error.SkipZigTest;
const gpa = testing.allocator;
diff --git a/test/selfmount.py b/test/selfmount.py
index 55809740..6534aa25 100644
--- a/test/selfmount.py
+++ b/test/selfmount.py
@@ -138,6 +138,13 @@ def run(binary):
check(run(f'python3 -c "{maker}" > {mount}/pane/{scratch}/ctl\n'.encode()) == '0', 'a 50 KB Edit block written through the mount')
body = client.read(f'/pane/{scratch}/body').decode().splitlines()
check(body == ['line %05d of the block' % i for i in range(2500)], f'it ran once, whole ({len(body)} lines)')
+ # Forty event reads held through the mount, a follower each, and
+ # the mount still answers the rest: 9ns keeps workers past what
+ # the editor holds (128), so `cat layout` is not queued behind
+ # them for ever.
+ held = run(f"for i in $(seq 40); do cat {mount}/pane/{first}/event > /dev/null & done; "
+ f"sleep 1; timeout 10 cat {mount}/layout > /dev/null; st=$?; kill $(jobs -p); exit $st\n".encode())
+ check(held == '0', f'cat layout answers beside 40 held event reads ({held!r})')
client.write('/exec', b'Msg still here\n')
text = ''.join(cell[0] for cell in client.screen()['cells'])
check('still here' in text, 'the editor answers after all of that')