Skip to content

Pool + Group: worker pool

Description

Pool + Group: worker pool.

  • Pool seeded with N empty containers, N workers spawned via Io.Group with a task index each.
  • Each worker gets its own container, writes its index, returns it.
  • group.cancel stops any workers still running, then pl.close frees the rest.
  • No mailbox — each worker's own container is the coordination surface.

Transfers (mailbox-less):

Diagram

 pool (N_WORKERS empty containers seeded — code=0)
 │ Io.Group (N_WORKERS workers, each with own task index at spawn time)
 ├──► worker 0 ──pl.get──► slot (empty) ──► ev.code = 0 ──► pl.put ──► pool
 ├──► worker 1 ──pl.get──► slot (empty) ──► ev.code = 1 ──► pl.put ──► pool
 └──► worker 2 ──pl.get──► slot (empty) ──► ev.code = 2 ──► pl.put ──► pool
 group.cancel ──► any worker that has not yet returned exits (all likely done)
 pl.close ──► on_close ──► freeList (remaining items freed)

Source

pub fn pool_group_worker_pool(allocator: std.mem.Allocator, io: std.Io) !void {
    var pool_ctx: hooks.AlwaysCreateHooks = .{ .alloc = allocator };
    const tags = [_]*const anyopaque{items.Event.EventPolyHelper.TAG};

    var pl_slot: Slot = null;
    try pool.new(io, allocator, pool_ctx.poolHooks(&tags), &pl_slot);
    const pl: *Pool = Pool.moveFromSlot(&pl_slot).?;

    try seedContainers(pl);

    var worker_ctxs: [N_WORKERS]WorkerCtx = undefined;
    var group: Io.Group = .init;
    try spawnWorkers(pl, io, &group, &worker_ctxs);

    std.log.info("master: {d} workers running, {d} empty containers in pool", .{ N_WORKERS, N_WORKERS });
    stopAndClosePool(pl, allocator, io, &group);
}

const N_WORKERS: usize = 3;

const WorkerCtx = struct {
    pl: *Pool,
    id: usize,
};

fn seedContainers(pl: *Pool) !void {
    for (0..N_WORKERS) |_| {
        var slot: Slot = null;
        try pl.get(items.Event.EventPolyHelper.TAG, .new_only, &slot);
        pl.put(&slot);
    }
}

fn workerFn(ctx: *WorkerCtx) error{Canceled}!void {
    var slot: Slot = null;
    defer ctx.pl.put(&slot);
    ctx.pl.get(items.Event.EventPolyHelper.TAG, .available_or_new, &slot) catch return;
    const ev: *items.Event = items.Event.EventPolyHelper.mustFromSlot(&slot);
    ev.code = @intCast(ctx.id);
    std.log.info("worker {d}: wrote task index into empty container (code={d})", .{ ctx.id, ev.code });
}

fn spawnWorkers(pl: *Pool, io: std.Io, group: *Io.Group, ctxs: *[N_WORKERS]WorkerCtx) !void {
    for (ctxs, 0..) |*ctx, i| {
        ctx.* = .{ .pl = pl, .id = i };
        try group.concurrent(io, workerFn, .{ctx});
    }
}

fn stopAndClosePool(pl: *Pool, alloc: std.mem.Allocator, io: std.Io, group: *Io.Group) void {
    group.cancel(io);
    std.log.info("master: all workers stopped via group.cancel", .{});
    pl.close();
    pool.destroy(pl, alloc);
    std.log.info("pool closed: on_close freed any remaining containers — no mailbox needed", .{});
}

const items = @import("../items/items.zig");
const hooks = @import("../hooks/hooks.zig");
const matryoshka = @import("matryoshka");
const std = @import("std");
const pool = matryoshka.pool;
const Pool = matryoshka.Pool;
const polynode = matryoshka.polynode;
const Slot = polynode.Slot;
const Io = std.Io;