Trailing data:
const Future = struct
const Future = struct {
runnable: Runnable,
func: *const fn (context: *const anyopaque, result: *anyopaque) void,
status: std.atomic.Value(Status),
/// On completion, increment this `u32` and do a futex wake on it.
awaiter: *std.atomic.Value(u32),
context_alignment: Alignment,
result_offset: usize,
alloc_len: usize,
const Status = packed struct(usize) {
/// The values of this enum are chosen so that await/cancel can just OR with 0b01 and 0b11
/// respectively. That *does* clobber `.done`, but that's actually fine, because if the tag
/// is `.done` then only the awaiter is referencing this `Future` anyway.
tag: enum(u2) {
/// The future is queued or running (depending on whether `thread` is set).
pending = 0b00,
/// Like `pending`, but the future is being awaited. `Future.awaiter` is populated.
pending_awaited = 0b01,
/// Like `pending`, but the future is being canceled. `Future.awaiter` is populated.
pending_canceled = 0b11,
/// The future has already completed. `thread` is `.null`, unless the future terminated
/// with an acknowledged cancel request, in which case `thread` is `.all_ones`.
done = 0b10,
},
/// When the future begins execution, this is atomically updated from `null` to the thread running the
/// `Future`, so that cancelation knows which thread to cancel.
thread: Thread.PackedPtr,
};
/// `Future.runnable.node` is `undefined` in the created `Future`.
fn create(
gpa: Allocator,
result_len: usize,
result_alignment: Alignment,
context: []const u8,
context_alignment: Alignment,
func: *const fn (context: *const anyopaque, result: *anyopaque) void,
) Allocator.Error!*Future {
const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Future);
const worst_case_context_offset = context_alignment.forward(@sizeOf(Future) + max_context_misalignment);
const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len);
const alloc_len = worst_case_result_offset + result_len;
const future: *Future = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Future), alloc_len)));
errdefer comptime unreachable;
const actual_context_addr = context_alignment.forward(@intFromPtr(future) + @sizeOf(Future));
const actual_result_addr = result_alignment.forward(actual_context_addr + context.len);
const actual_result_offset = actual_result_addr - @intFromPtr(future);
future.* = .{
.runnable = .{
.node = undefined,
.startFn = &start,
},
.func = func,
.status = .init(.{
.tag = .pending,
.thread = .null,
}),
.awaiter = undefined,
.context_alignment = context_alignment,
.result_offset = actual_result_offset,
.alloc_len = alloc_len,
};
@memcpy(future.contextPointer()[0..context.len], context);
return future;
}
fn destroy(future: *Future, gpa: Allocator) void {
const base: [*]align(@alignOf(Future)) u8 = @ptrCast(future);
gpa.free(base[0..future.alloc_len]);
}
fn resultPointer(future: *Future) [*]u8 {
const base: [*]u8 = @ptrCast(future);
return base + future.result_offset;
}
fn contextPointer(future: *Future) [*]u8 {
const base: [*]u8 = @ptrCast(future);
const context_offset = future.context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)) - @intFromPtr(future);
return base + context_offset;
}
fn start(r: *Runnable, thread: *Thread, t: *Threaded) void {
_ = t;
const future: *Future = @fieldParentPtr("runnable", r);
thread.status.store(.{
.cancelation = .none,
.awaitable = .fromFuture(future),
}, .monotonic);
{
const old_status = future.status.fetchOr(.{
.tag = .pending,
.thread = .pack(thread),
}, .release);
assert(old_status.thread == .null);
switch (old_status.tag) {
.pending, .pending_awaited => {},
.pending_canceled => thread.status.store(.{
.cancelation = .canceling,
.awaitable = .fromFuture(future),
}, .monotonic),
.done => unreachable,
}
}
future.func(future.contextPointer(), future.resultPointer());
const had_acknowledged_cancel = switch (thread.status.load(.monotonic).cancelation) {
.none, .canceling => false,
.canceled => true,
.parked => unreachable,
.blocked => unreachable,
.blocked_alertable => unreachable,
.blocked_alertable_canceling => unreachable,
.blocked_canceling => unreachable,
};
thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
const old_status = future.status.swap(.{
.tag = .done,
.thread = if (had_acknowledged_cancel) .all_ones else .null,
}, .acq_rel); // acquire `future.awaiter`, release results
switch (old_status.tag) {
.pending => {},
.pending_awaited, .pending_canceled => {
const to_signal = future.awaiter;
_ = to_signal.fetchAdd(1, .release); // release results
Thread.futexWake(&to_signal.raw, 1);
},
.done => unreachable,
}
}
/// The caller has canceled `future`. `thread` is the thread currently running that future.
/// Inform `thread` of the cancelation if necessary, and wait for `future` to finish (indicated
/// by `num_completed` being incremented from 0 to 1), while sending regular signals to `thread`
/// if necessary for it to unblock from a cancelable syscall.
fn waitForCancelWithSignaling(
future: *Future,
t: *Threaded,
num_completed: *std.atomic.Value(u32),
thread: ?*Thread,
) void {
var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false;
var timeout_ns: u64 = 1 << 10;
while (true) {
need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future));
Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
switch (num_completed.load(.acquire)) { // acquire task results
0 => {},
1 => break,
else => unreachable,
}
timeout_ns <<|= 1;
}
}
}