Timer via mailbox
Description
Timer via mailbox.
- Separate timer task sends 2 Timer ticks into the master's inbox.
- Main sends 2 Events into the same inbox.
- Worker dispatches on tag: counts Events separately from Timer ticks.
- Worker exits after receiving the expected total — no Select needed.
Diagram
main ──Event×2──►
timerFn ──Timer×2──► mailbox ──► workerFn (tag dispatch; fixed count)
(workerFn exits after receiving N_EVENTS + N_TICKS items)
fut_timer.await → fut_worker.await
Source
pub fn timer_via_mailbox(allocator: std.mem.Allocator, io: std.Io) !void {
const mbh: MailboxHandle = try mailbox.new(io, allocator);
defer {
var rem: std.DoublyLinkedList = mailbox.close(mbh);
items.freeList(&rem, allocator);
mailbox.destroy(mbh, allocator);
}
try sendEvents(mbh, allocator, N_EVENTS);
var worker_ctx: WorkerCtx = .{
.mbh = mbh,
.alloc = allocator,
.expected = N_EVENTS + N_TICKS,
};
try spawnAndAwait(mbh, allocator, io, &worker_ctx);
try helpers.expect(error.TimerViaMailboxFailed, worker_ctx.event_count == N_EVENTS, "expected 2 Events");
try helpers.expect(error.TimerViaMailboxFailed, worker_ctx.timer_count == N_TICKS, "expected 2 timer ticks");
std.log.info("done: {d} events, {d} timer ticks — tag dispatch via single mailbox", .{
worker_ctx.event_count,
worker_ctx.timer_count,
});
}
const TICK_NS: i96 = 50_000_000; // 50 ms
const N_EVENTS: usize = 2;
const N_TICKS: usize = 2;
const TimerCtx = struct {
mbh: MailboxHandle,
alloc: std.mem.Allocator,
io: std.Io,
};
fn timerFn(ctx: *TimerCtx) anyerror!void {
const sleep_t: std.Io.Timeout = .{
.duration = .{ .raw = .{ .nanoseconds = TICK_NS }, .clock = .real },
};
for (0..N_TICKS) |_| {
try std.Io.Timeout.sleep(sleep_t, ctx.io);
var slot: Slot = null;
try items.Timer.TimerPolyHelper.create(ctx.alloc, &slot);
mailbox.send(ctx.mbh, &slot) catch {
items.freeSlot(&slot, ctx.alloc);
return;
};
}
}
const WorkerCtx = struct {
mbh: MailboxHandle,
alloc: std.mem.Allocator,
expected: usize,
timer_count: usize = 0,
event_count: usize = 0,
};
fn workerFn(ctx: *WorkerCtx) anyerror!void {
var received: usize = 0;
while (received < ctx.expected) {
var slot: Slot = null;
defer items.freeSlot(&slot, ctx.alloc);
try mailbox.receive(ctx.mbh, &slot, null);
received += 1;
if (items.Timer.TimerPolyHelper.identifySlotAs(&slot)) |_| {
ctx.timer_count += 1;
std.log.info("worker: timer tick {d}", .{ctx.timer_count});
} else if (items.Event.EventPolyHelper.identifySlotAs(&slot)) |ev| {
ctx.event_count += 1;
std.log.info("worker: Event code={d} (event {d})", .{ ev.code, ctx.event_count });
}
}
}
fn sendEvents(mbh: MailboxHandle, alloc: std.mem.Allocator, count: usize) !void {
for (0..count) |i| {
var slot: Slot = null;
defer items.Event.EventPolyHelper.destroy(alloc, &slot);
try items.Event.EventPolyHelper.create(alloc, &slot);
items.Event.EventPolyHelper.mustIdentifySlotAs(&slot).code = @intCast(i + 1);
try mailbox.send(mbh, &slot);
}
}
fn spawnAndAwait(mbh: MailboxHandle, alloc: std.mem.Allocator, io: std.Io, worker_ctx: *WorkerCtx) !void {
var timer_ctx: TimerCtx = .{ .mbh = mbh, .alloc = alloc, .io = io };
var fut_timer = try io.concurrent(timerFn, .{&timer_ctx});
var fut_worker = try io.concurrent(workerFn, .{worker_ctx});
errdefer fut_worker.cancel(io) catch {};
try fut_timer.await(io);
try fut_worker.await(io);
}
const items = @import("../items/items.zig");
const helpers = @import("../helpers/helpers.zig");
const matryoshka = @import("matryoshka");
const std = @import("std");
const mailbox = matryoshka.mailbox;
const polynode = matryoshka.polynode;
const PolyNode = polynode.PolyNode;
const Slot = polynode.Slot;
const MailboxHandle = mailbox.MailboxHandle;