diff options
| -rw-r--r-- | build.zig.zon | 4 | ||||
| -rw-r--r-- | docs/fs.md | 7 | ||||
| -rw-r--r-- | src/9p.zig | 8 | ||||
| -rw-r--r-- | src/9p_io.zig | 5 | ||||
| -rw-r--r-- | test/selfmount.py | 7 |
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 @@ -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 @@ -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') |
