From 7de6d8304f563023c4a5fa9f8223f9ed626c5f91 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 17:51:35 -0300 Subject: Sixteen 9P connections, and the next one is told there is no room instead of being reset Four slots ran out under scripts plus a mount, and the fifth client was closed without a word, which through a mount looks exactly like a dead session. pardes now serves sixteen, and builds against cloud9 09b77cf, whose runner answers the Tversion of a client it cannot seat with an Rerror "too many connections" and tells pardes, which logs `err - 9p: too many connections`. QUIC keeps its own sixteen and still just closes. Co-Authored-By: Claude Opus 5.5 --- src/9p_io.zig | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/9p_io.zig b/src/9p_io.zig index 6bd03049..8f56ff13 100644 --- a/src/9p_io.zig +++ b/src/9p_io.zig @@ -64,7 +64,9 @@ const prefix = "pardes-9p-"; pub const msize = ninep.msize; -pub const max_conns = 4; +/// Scripts, a mount and an agent or two at once; each slot holds its +/// buffers (a few msize) whether used or not. +pub const max_conns = 16; extern "c" fn inet_pton(family: c_int, src: [*:0]const u8, dst: *anyopaque) c_int; @@ -176,6 +178,8 @@ pub const Listener = struct { stopping: std.atomic.Value(bool) = .init(false), /// A connection wrote the Restore and has yet to answer it. restore_writer: std.atomic.Value(bool) = .init(false), + /// Clients turned away with every slot taken, since the log last said so. + refused: std.atomic.Value(u32) = .init(0), /// Connections accepted so far, and the count each slot's connection /// was accepted at: a Restore cuts those accepted before it (`reset`), /// and a new client in a freed slot has a later stamp. Written on the @@ -217,6 +221,14 @@ pub const Listener = struct { /// answers. A request that would change a pane while the editor is out /// in a syscall mid-step is parked in the engine instead, and retried /// when the turn is next given up between steps (`wakeParked`). + /// On the accepting task, without the turn: counted, and logged with + /// the turn (`answerHeld`). + fn onRefused(ctx: ?*anyopaque) void { + const l = of(ctx); + _ = l.refused.fetchAdd(1, .acq_rel); + l.kick(); + } + fn onOpened(ctx: ?*anyopaque, conn: *Runner.Conn) void { const l = of(ctx); l.accepted[conn.index].store(l.accepts.fetchAdd(1, .acq_rel) + 1, .release); @@ -331,6 +343,11 @@ pub const Listener = struct { const l = of(ctx); if (l.stopping.load(.acquire)) return; const core = l.core; + const refused = l.refused.swap(0, .acq_rel); + if (refused != 0) { + var why: [64]u8 = undefined; + pardes.ctlfs.events.notePath(core, "err -", std.fmt.bufPrint(&why, "9p: too many connections ({d} turned away)", .{refused}) catch "9p: too many connections"); + } if (!core.fs.news) return; core.fs.news = false; for (&core.fs.opens) |*o| { @@ -824,7 +841,7 @@ pub fn listen(io: std.Io, gpa: std.mem.Allocator, core: *pardes.Pardes, named: [ l.runner.init(.{ .io = io, .root = pardes.ctlfs.root, - .handler = .{ .ctx = l, .serve = Listener.onServe, .opened = Listener.onOpened }, + .handler = .{ .ctx = l, .serve = Listener.onServe, .opened = Listener.onOpened, .refused = Listener.onRefused }, .greet_timeout_ms = Listener.greet_deadline_ms, }); const entry_name = if (named.len != 0) named else fallback; @@ -1356,7 +1373,7 @@ test "Unix TCP and QUIC share one listener through reads writes reconnects and r } try testing.expect(l.path().len != 0); try testing.expectEqual(tcp, l.tcp_address != null); - try testing.expectEqual(@as(usize, 4), l.runner.conns.len); + try testing.expectEqual(@as(usize, max_conns), l.runner.conns.len); try testing.expect(l.watcher == null); const port = (if (tcp) l.tcp_address else l.quic_address).?.getPort(); try testing.expect(port != 0); -- cgit v1.3