Skip to content

Select mailbox close propagation

Description

Select mailbox close propagation.

  • Mailbox is empty; receiveResult blocks as a Select event source.
  • A timer triggers first, closes the mailbox while receiveResult is running.
  • The blocked receive unblocks with .closed, propagated through sel.await().

Diagram

 mailbox (empty)
 │ receiveResult (blocking)
 Select(MasterEvent) ◄── sleepFn (timer triggers first)
 .timer ──► mailbox.close(mbh) ──► freeList(rem)
            (running receiveResult unblocks with .closed)
 sel.await() ──► .inbox .closed
 sel.cancelDiscard()

Source

pub fn select_mailbox_close_propagation(allocator: std.mem.Allocator, io: std.Io) !void {
    const mbh: MailboxHandle = try mailbox.new(io, allocator);
    var ctx: Ctx = .{ .mbh = mbh, .alloc = allocator, .io = io };
    defer {
        if (!ctx.mbh_closed) {
            var rem: std.DoublyLinkedList = mailbox.close(ctx.mbh);
            items.freeList(&rem, ctx.alloc);
        }
        mailbox.destroy(ctx.mbh, ctx.alloc);
    }

    var buf: [4]MasterEvent = undefined;
    var sel: std.Io.Select(MasterEvent) = std.Io.Select(MasterEvent).init(io, &buf);
    try ctx.setupSelect(&sel);
    try ctx.runEventLoop(&sel);

    try helpers.expect(error.SelectMailboxCloseFailed, ctx.got_closed, "expected .closed from Select inbox");
    std.log.info("done: mailbox.close propagated .closed through Select", .{});
}

const TIMER_NS: i96 = 10_000_000; // 10 ms

const MasterEvent = union(enum) {
    inbox: mailbox.ReceiveResult,
    timer: void,
};

fn sleepFn(sleep_t: std.Io.Timeout, io: std.Io) void {
    std.Io.Timeout.sleep(sleep_t, io) catch {};
}

const Ctx = struct {
    mbh: MailboxHandle,
    alloc: std.mem.Allocator,
    io: std.Io,
    mbh_closed: bool = false,
    got_closed: bool = false,

    fn setupSelect(self: *Ctx, sel: *std.Io.Select(MasterEvent)) !void {
        const sleep_t: std.Io.Timeout = .{
            .duration = .{ .raw = .{ .nanoseconds = TIMER_NS }, .clock = .real },
        };
        try sel.concurrent(.inbox, mailbox.receiveResult, .{ self.mbh, null });
        try sel.concurrent(.timer, sleepFn, .{ sleep_t, self.io });
    }

    fn runEventLoop(self: *Ctx, sel: *std.Io.Select(MasterEvent)) !void {
        loop: while (true) {
            const event: MasterEvent = try sel.await();
            switch (event) {
                .timer => {
                    std.log.info("timer: closing mailbox while receiveResult is running", .{});
                    var rem: std.DoublyLinkedList = mailbox.close(self.mbh);
                    items.freeList(&rem, self.alloc);
                    self.mbh_closed = true;
                },
                .inbox => |r| switch (r) {
                    .closed => {
                        std.log.info("inbox: .closed — mailbox.close propagated into Select", .{});
                        self.got_closed = true;
                        break :loop;
                    },
                    .item => |handle| {
                        var slot: Slot = handle;
                        items.freeSlot(&slot, self.alloc);
                    },
                    .canceled, .timeout, .wakeup => break :loop,
                },
            }
        }
        sel.cancelDiscard();
    }
};

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 Slot = polynode.Slot;
const MailboxHandle = mailbox.MailboxHandle;