Pool holds pools at teardown
Description
Pool holds pools at teardown.
- A carrier pool's hooks accept Pool items (Pool.TAG).
- Two inner pools are stored in the carrier via put.
- close on the carrier walks the returned list, closes and destroys each inner pool.
- Shows uniform cleanup of infrastructure items — no per-instance role discrimination needed.
Diagram
pool.new × 2 ──► carrier.put ──► carrier pool (holds inner pools as items)
│ carrier.close
▼
on_close ──► close + destroy per inner pool
Source
pub fn pool_holds_pools_at_teardown(allocator: std.mem.Allocator, io: std.Io) !void {
// Carrier pool — holds inner pools as items.
var carrier_ctx: CarrierCtx = .{ .alloc = allocator };
const carrier_tags = [_]*const anyopaque{Pool.TAG};
var carrier_slot: Slot = null;
try pool.new(io, allocator, .{
.ctx = &carrier_ctx,
.tags = &carrier_tags,
.on_get = onGet,
.on_put = onPut,
.on_close = onClose,
}, &carrier_slot);
const carrier: *Pool = Pool.moveFromSlot(&carrier_slot).?;
const n: usize = 2;
var ctx: Ctx = .{
.carrier = carrier,
.alloc = allocator,
.io = io,
.inner_ctx = .{ .alloc = allocator },
};
try ctx.createAndStoreInnerPools(n);
try ctx.closeCarrier(&carrier_ctx, n);
}
const inner_tags = [_]*const anyopaque{Pool.TAG};
const CarrierCtx = struct {
alloc: std.mem.Allocator,
closed_count: usize = 0,
};
fn onGet(_: *anyopaque, _: *const anyopaque, _: usize, _: *Slot) void {}
fn resetOnPut(_: *Slot) void {} // Pool items carry no resettable scalar state — kept for on_put-shape consistency
fn onPut(_: *anyopaque, _: usize, slot: *Slot) ?polynode.ItemList {
resetOnPut(slot);
return null;
}
fn onClose(ctx_opaque: *anyopaque, list: *polynode.ItemList) void {
const ctx: *CarrierCtx = @ptrCast(@alignCast(ctx_opaque));
while (list.popFirst()) |poly| {
const pl: *Pool = Pool.mustFromPoly(poly);
pl.close();
pool.destroy(pl, ctx.alloc);
ctx.closed_count += 1;
}
std.log.info("on_close: closed and destroyed {d} inner pool(s)", .{ctx.closed_count});
}
const Ctx = struct {
carrier: *Pool,
alloc: std.mem.Allocator,
io: std.Io,
inner_ctx: CarrierCtx,
/// Hooks for an inner pool.
///
/// An inner pool is cargo: it is created, stored in the carrier and closed
/// at teardown, and no item ever passes through it. It still needs a full
/// hook set, because `pool.new` registers one and `close` calls `on_close`.
fn innerHooks(self: *Ctx) Pool.Hooks {
return .{
.ctx = &self.inner_ctx,
.tags = &inner_tags,
.on_get = onGet,
.on_put = onPut,
.on_close = onClose,
};
}
fn createAndStoreInnerPools(self: *Ctx, n: usize) !void {
var j: usize = 0;
while (j < n) : (j += 1) {
var slot: Slot = null;
try pool.new(self.io, self.alloc, self.innerHooks(), &slot);
self.carrier.put(&slot);
try helpers.expect(error.PoolAsItemFailed, slot == null, "carrier did not accept inner pool");
std.log.info("stored inner pool {d} in carrier", .{j + 1});
}
}
fn closeCarrier(self: *Ctx, carrier_ctx: *CarrierCtx, n: usize) !void {
// Tag dispatch is not needed here: all items are pools by construction.
self.carrier.close();
try helpers.expect(error.PoolAsItemFailed, carrier_ctx.closed_count == n, "wrong number of inner pools cleaned up");
std.log.info("carrier closed: {d} inner pool(s) cleaned up", .{carrier_ctx.closed_count});
pool.destroy(self.carrier, self.alloc);
}
};
const helpers = @import("../helpers/helpers.zig");
const matryoshka = @import("matryoshka");
const std = @import("std");
const pool = matryoshka.pool;
const polynode = matryoshka.polynode;
const Slot = polynode.Slot;
const Pool = matryoshka.Pool;