diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/fs.zig | 68 | ||||
| -rw-r--r-- | src/serve.zig | 16 |
2 files changed, 79 insertions, 5 deletions
@@ -467,8 +467,40 @@ pub fn Server(comptime Backend: type, comptime opts: Options) type { return .{ .protocol = .init(.{ .in = o.in, .out = o.out }), .root = o.root, .hash_seed = fmix32(o.seed) }; } + /// `init`, in place. With `Options.fid_index` the fid table is + /// bump-allocated and nothing reads a slot at or past `high_water`, + /// so those slots are left unwritten: a large `fid_capacity` then + /// costs memory only for the fids a client actually holds, where + /// `init` writes the whole table on every connection. + pub fn initIn(s: *Self, o: InitOptions) void { + assert(o.in.len >= msize_min); + assert(o.out.len >= 2 * msize_min); + assert(o.root != 0); + s.protocol = .init(.{ .in = o.in, .out = o.out }); + s.root = o.root; + s.uname = @splat(0); + s.uname_len = 0; + if (!opts.fid_index) s.fids = @splat(.{}); + s.slots = @splat(.{}); + s.job = .{}; + s.seq = 0; + s.nfids = 0; + s.norphans = 0; + s.index = @splat(no_slot); + s.hash_seed = fmix32(o.seed); + s.free_head = no_slot; + s.high_water = 0; + } + + /// The fid slots that may hold anything: all of them, or with + /// `Options.fid_index` the bump-allocated prefix. + fn touched(s: *Self) []Fid { + return if (opts.fid_index) s.fids[0..s.high_water] else s.fids[0..]; + } + pub fn references(s: *const Self, node: u64) bool { - for (s.fids) |fid| if (fid.used and fid.node == node) return true; + const held = if (opts.fid_index) s.fids[0..s.high_water] else s.fids[0..]; + for (held) |fid| if (fid.used and fid.node == node) return true; if (s.job.kind != .none and s.job.node == node) return true; for (s.slots) |slot| if (slot.used and slot.req.node == node) return true; return false; @@ -485,7 +517,7 @@ pub fn Server(comptime Backend: type, comptime opts: Options) type { } fn reset(s: *Self) void { - for (&s.fids, 0..) |*f, i| { + for (s.touched(), 0..) |*f, i| { if (!f.used or f.orphan) continue; if (f.open or refs) s.orphanFid(i) else s.releaseSlot(i); } @@ -754,7 +786,7 @@ pub fn Server(comptime Backend: type, comptime opts: Options) type { fn orphan(s: *Self) ?Backend.Req { if (s.norphans == 0) return null; - for (&s.fids, 0..) |*f, i| { + for (s.touched(), 0..) |*f, i| { if (!f.used or !f.orphan) continue; assert(f.open or refs); s.norphans -= 1; @@ -3358,6 +3390,36 @@ fn checkFidIndex(s: *const IndexRig.S) !void { try testing.expectEqual(used, s.fidCount()); } +test "fs server: an engine set up in place never writes the fid slots it has not used" { + // What a runner's connection slot holds before a client takes it: + // memory nobody wrote, here made loud with a pattern. + const r = try testing.allocator.create(IndexRig); + defer testing.allocator.destroy(r); + r.* = .{ .fsys = .{} }; + @memset(std.mem.asBytes(&r.srv), 0xA5); + r.srv.initIn(.{ .in = &r.in, .out = &r.out, .root = 1, .seed = 0x4242 }); + _ = try r.call(notag, .{ .tversion = .{ .msize = 4096, .version = "9P2000" } }); + _ = try r.call(0, .{ .tattach = .{ .fid = 0, .afid = nofid, .uname = "goblin", .aname = "" } }); + // Walk a few hundred fids, clunk half, walk again (the free list), and + // hang up (the reset walks every used slot). + var i: u32 = 1; + while (i <= 300) : (i += 1) _ = try r.walkTo(0, i, &.{ "1", "body" }); + i = 1; + while (i <= 300) : (i += 2) _ = try r.call(2, .{ .tclunk = .{ .fid = i } }); + i = 1; + while (i <= 300) : (i += 2) _ = try r.walkTo(0, 1000 + i, &.{"1"}); + try testing.expectEqual(@as(u16, 301), r.srv.high_water); + try testing.expectEqual(@as(usize, 301), r.srv.fidCount()); + for (r.srv.fids[0..r.srv.high_water], 0..) |*f, k| { + if (f.used and !f.orphan) try testing.expectEqual(k, r.srv.findFid(f.fid).?); + } + // Every slot past the high-water mark is still the pattern. + for (std.mem.sliceAsBytes(r.srv.fids[r.srv.high_water..])) |b| try testing.expectEqual(@as(u8, 0xA5), b); + r.srv.hangup(); + try testing.expectEqual(@as(usize, 0), r.srv.fidCount()); + for (std.mem.sliceAsBytes(r.srv.fids[301..])) |b| try testing.expectEqual(@as(u8, 0xA5), b); +} + /// Fid numbers chosen to stress the index: dense low ids, ids with only high /// bits set, and ids counting down from 2^32-1 (all distinct for i < 2^20). fn adversarialId(i: u32) u32 { diff --git a/src/serve.zig b/src/serve.zig index 10c8d01..0311f3d 100644 --- a/src/serve.zig +++ b/src/serve.zig @@ -178,7 +178,7 @@ pub fn Runner(comptime Backend: type, comptime opts: fs.Options, comptime limits c.room = .unset; // Under the lock: the slot is already visible as live. c.lock(); - c.engine = .init(.{ .in = &c.in, .out = &c.out, .root = r.root, .seed = r.seed ^ c.index }); + c.engine.initIn(.{ .in = &c.in, .out = &c.out, .root = r.root, .seed = r.seed ^ c.index }); c.unlock(); if (r.handler.opened) |f| f(r.handler.ctx, c); } @@ -308,7 +308,19 @@ pub fn Runner(comptime Backend: type, comptime opts: fs.Options, comptime limits r.slots = .init; r.stopping = .init(false); r.active = .init(0); - for (&r.conns, 0..) |*c, i| c.* = .{ .runner = r, .index = @intCast(i) }; + // Field by field: a connection's engine and buffers are written + // when a client takes the slot (`start`), so a runner sized for + // many connections costs nothing for the ones never used. + for (&r.conns, 0..) |*c, i| { + c.runner = r; + c.index = @intCast(i); + c.user = null; + c.mutex = .init; + c.flags = .init(0); + c.signal = .unset; + c.room = .unset; + c.used = .init(false); + } } /// Binds `address` and starts accepting on it. Returns the bound |
