summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/fs.zig68
-rw-r--r--src/serve.zig16
2 files changed, 79 insertions, 5 deletions
diff --git a/src/fs.zig b/src/fs.zig
index 83cea68..ef18a9d 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -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