feature. See also
. The project being documented here (as the example) is the Zig library itself.
Io.Condition
pub const Condition = struct
File
Code
pub const Condition = struct {
state: std.atomic.Value(State),
epoch: std.atomic.Value(u32),
const State = packed struct(u32) {
waiters: u16,
signals: u16,
};
pub const init: Condition = .{
.state = .init(.{ .waiters = 0, .signals = 0 }),
.epoch = .init(0),
};
pub fn wait(cond: *Condition, io: Io, mutex: *Mutex) Cancelable!void {
waitTimeout(cond, io, mutex, .none) catch |err| switch (err) {
error.Timeout => unreachable,
error.Canceled => |e| return e,
};
}
pub const WaitTimeoutError = Cancelable || Timeout.Error;
pub fn waitTimeout(cond: *Condition, io: Io, mutex: *Mutex, timeout: Timeout) WaitTimeoutError!void {
const deadline = timeout.toDeadline(io);
var epoch = cond.epoch.load(.acquire);
{
const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic);
assert(prev_state.waiters < math.maxInt(u16));
}
mutex.unlock(io);
defer mutex.lockUncancelable(io);
while (true) {
const result = io.futexWaitTimeout(u32, &cond.epoch.raw, epoch, deadline);
epoch = cond.epoch.load(.acquire);
// Even on error, try to consume a pending signal first. Otherwise a race might
// cause a signal to get stuck in the state with no corresponding waiter.
{
var prev_state = cond.state.load(.monotonic);
while (prev_state.signals > 0) {
prev_state = cond.state.cmpxchgWeak(prev_state, .{
.waiters = prev_state.waiters - 1,
.signals = prev_state.signals - 1,
}, .acquire, .monotonic) orelse {
return;
};
}
}
// was an error, we will remove ourselves as a waiter and return that error. If a
// timeout was specified and the deadline has passed, we remove ourselves as a waiter
// and return `error.Timeout`. Otherwise, we'll loop back to the futex wait.
result catch |err| {
const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic);
assert(prev_state.waiters > 0);
return err;
};
switch (deadline) {
.none => {},
.deadline => |d| if (d.untilNow(io).raw.nanoseconds >= 0) {
const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic);
assert(prev_state.waiters > 0);
return error.Timeout;
},
.duration => unreachable,
}
}
}
pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void {
var epoch = cond.epoch.load(.acquire);
{
const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic);
assert(prev_state.waiters < math.maxInt(u16));
}
mutex.unlock(io);
defer mutex.lockUncancelable(io);
while (true) {
io.futexWaitUncancelable(u32, &cond.epoch.raw, epoch);
epoch = cond.epoch.load(.acquire);
// Even on error, try to consume a pending signal first. Otherwise a race might
// cause a signal to get stuck in the state with no corresponding waiter.
{
var prev_state = cond.state.load(.monotonic);
while (prev_state.signals > 0) {
prev_state = cond.state.cmpxchgWeak(prev_state, .{
.waiters = prev_state.waiters - 1,
.signals = prev_state.signals - 1,
}, .acquire, .monotonic) orelse {
return;
};
}
}
// so we'll loop back to the futex wait.
}
}
pub fn signal(cond: *Condition, io: Io) void {
var prev_state = cond.state.load(.monotonic);
while (prev_state.waiters > prev_state.signals) {
@branchHint(.unlikely);
prev_state = cond.state.cmpxchgWeak(prev_state, .{
.waiters = prev_state.waiters,
.signals = prev_state.signals + 1,
}, .release, .monotonic) orelse {
// Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen
// between it observing the epoch and sleeping on it, but this is extraordinarily
// unlikely due to the precise number of calls required.
_ = cond.epoch.fetchAdd(1, .release);
io.futexWake(u32, &cond.epoch.raw, 1);
return;
};
}
}
pub fn broadcast(cond: *Condition, io: Io) void {
var prev_state = cond.state.load(.monotonic);
while (prev_state.waiters > prev_state.signals) {
@branchHint(.unlikely);
prev_state = cond.state.cmpxchgWeak(prev_state, .{
.waiters = prev_state.waiters,
.signals = prev_state.waiters,
}, .release, .monotonic) orelse {
// Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen
// between it observing the epoch and sleeping on it, but this is extraordinarily
// unlikely due to the precise number of calls required.
_ = cond.epoch.fetchAdd(1, .release);
io.futexWake(u32, &cond.epoch.raw, prev_state.waiters - prev_state.signals);
return;
};
}
}
}