feature. See also
. The project being documented here (as the example) is the Zig library itself.
Dispatch.Fiber
const Fiber = struct
File
Code
const Fiber = struct {
required_align: void align(4),
evented: *Evented,
context: Io.fiber.Context,
link: union {
awaiter: ?*Fiber,
group: struct { prev: ?*Fiber, next: ?*Fiber },
},
awaiting_group: Group,
cancel_status: CancelStatus,
cancel_protection: CancelProtection,
var next_name: u64 = 0;
const CancelStatus = packed struct(usize) {
requested: bool,
awaiting: Awaiting,
const unrequested: CancelStatus = .{ .requested = false, .awaiting = .nothing };
const Awaiting = enum(@Int(.unsigned, @bitSizeOf(usize) - shift)) {
nothing = 0,
group = 1,
_,
const shift = 1;
fn subWrap(lhs: Awaiting, rhs: Awaiting) Awaiting {
return @fromBackingInt(@intCast(@backingInt(lhs) -% @backingInt(rhs)));
}
fn fromCancelable(cancelable: *Cancelable) Awaiting {
return @fromBackingInt(@intCast(@shrExact(@intFromPtr(cancelable), shift)));
}
fn toCancelable(awaiting: Awaiting) *Cancelable {
return @ptrFromInt(@shlExact(@as(usize, @backingInt(awaiting)), shift));
}
};
fn changeAwaiting(
cancel_status: *CancelStatus,
old_awaiting: Awaiting,
new_awaiting: Awaiting,
) bool {
const old_cancel_status = @atomicRmw(CancelStatus, cancel_status, .Add, .{
.requested = false,
.awaiting = new_awaiting.subWrap(old_awaiting),
}, .release);
assert(old_cancel_status.awaiting == old_awaiting);
return old_cancel_status.requested;
}
};
const CancelProtection = packed struct {
user: Io.CancelProtection,
acknowledged: bool,
const unblocked: CancelProtection = .{ .user = .unblocked, .acknowledged = false };
fn check(cancel_protection: CancelProtection) Io.CancelProtection {
return @fromBackingInt(@intCast(@intFromBool(cancel_protection != unblocked)));
}
fn acknowledge(cancel_protection: *CancelProtection) void {
assert(!cancel_protection.acknowledged);
cancel_protection.acknowledged = true;
}
fn recancel(cancel_protection: *CancelProtection) void {
assert(cancel_protection.acknowledged);
cancel_protection.acknowledged = false;
}
test check {
try std.testing.expectEqual(Io.CancelProtection.unblocked, check(.unblocked));
try std.testing.expectEqual(Io.CancelProtection.blocked, check(.{
.user = .unblocked,
.acknowledged = true,
}));
try std.testing.expectEqual(Io.CancelProtection.blocked, check(.{
.user = .blocked,
.acknowledged = false,
}));
try std.testing.expectEqual(Io.CancelProtection.blocked, check(.{
.user = .blocked,
.acknowledged = true,
}));
}
};
const finished: ?*Fiber = @ptrFromInt(@alignOf(Fiber));
const max_result_align: Alignment = .@"16";
const max_result_size = max_result_align.forward(512);
const min_stack_size = 60 * 1024 * 1024;
const max_context_align: Alignment = .@"16";
const max_context_size = max_context_align.forward(1024);
const max_closure_size: usize = @sizeOf(AsyncClosure);
const max_closure_align: Alignment = .of(AsyncClosure);
const allocation_size = std.mem.alignForward(
usize,
max_closure_align.max(max_context_align).forward(
max_result_align.forward(@sizeOf(Fiber)) + max_result_size + min_stack_size,
) + max_closure_size + max_context_size,
std.heap.page_size_max,
);
fn create(ev: *Evented) error{OutOfMemory}!*Fiber {
return @ptrCast(try ev.allocator().alignedAlloc(u8, .of(Fiber), allocation_size));
}
fn destroy(fiber: *Fiber, ev: *Evented) void {
ev.allocator().free(fiber.allocatedSlice());
}
fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 {
return @as([*]align(@alignOf(Fiber)) u8, @ptrCast(f))[0..allocation_size];
}
fn allocatedEnd(f: *Fiber) [*]u8 {
const allocated_slice = f.allocatedSlice();
return allocated_slice[allocated_slice.len..].ptr;
}
fn resultPointer(f: *Fiber, comptime Result: type) *Result {
return @ptrCast(@alignCast(f.resultBytes(.of(Result))));
}
fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {
return @ptrFromInt(alignment.forward(@intFromPtr(f) + @sizeOf(Fiber)));
}
const Queue = struct { head: *Fiber, tail: *Fiber };
const PackedPtr = enum(@Int(.unsigned, @bitSizeOf(usize) - 2)) {
null = 0,
all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 2)),
_,
const Split = packed struct(usize) { low: u2, high: PackedPtr };
fn pack(ptr: ?*Fiber) PackedPtr {
const split: Split = @bitCast(@intFromPtr(ptr));
assert(split.low == 0);
return split.high;
}
fn unpack(ptr: PackedPtr) ?*Fiber {
const split: Split = .{ .low = 0, .high = ptr };
return @ptrFromInt(@as(usize, @bitCast(split)));
}
};
fn requestCancel(fiber: *Fiber, ev: *Evented) void {
const cancel_status = @atomicRmw(
Fiber.CancelStatus,
&fiber.cancel_status,
.Or,
.{ .requested = true, .awaiting = .nothing },
.acquire,
);
assert(!cancel_status.requested);
switch (cancel_status.awaiting) {
.nothing => {},
.group => {
// so propagate the cancelation to the group.
if (fiber.awaiting_group.cancel(ev, null)) {
fiber.awaiting_group = undefined;
ev.queue.async(fiber, &Fiber.@"resume");
}
},
_ => |awaiting| awaiting.toCancelable().async(),
}
}
fn @"resume"(context: ?*anyopaque) callconv(.c) void {
const fiber: *Fiber = @ptrCast(@alignCast(context));
const thread: *Thread = .current();
const message: SwitchMessage = .{
.contexts = .{
.old = &thread.main_context,
.new = &fiber.context,
},
.pending_task = .nothing,
};
contextSwitch(&message).handle(fiber.evented);
}
}