Skip to content

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;