zig/lib/std/Thread/StaticResetEvent.zig
Andrew Kelley d29871977f remove redundant license headers from zig standard library
We already have a LICENSE file that covers the Zig Standard Library. We
no longer need to remind everyone that the license is MIT in every single
file.

Previously this was introduced to clarify the situation for a fork of
Zig that made Zig's LICENSE file harder to find, and replaced it with
their own license that required annual payments to their company.
However that fork now appears to be dead. So there is no need to
reinforce the copyright notice in every single file.
2021-08-24 12:25:09 -07:00

395 lines
14 KiB
Zig

//! A thread-safe resource which supports blocking until signaled.
//! This API is for kernel threads, not evented I/O.
//! This API is statically initializable. It cannot fail to be initialized
//! and it requires no deinitialization. The downside is that it may not
//! integrate as cleanly into other synchronization APIs, or, in a worst case,
//! may be forced to fall back on spin locking. As a rule of thumb, prefer
//! to use `std.Thread.ResetEvent` when possible, and use `StaticResetEvent` when
//! the logic needs stronger API guarantees.
const std = @import("../std.zig");
const StaticResetEvent = @This();
const assert = std.debug.assert;
const os = std.os;
const time = std.time;
const linux = std.os.linux;
const windows = std.os.windows;
const testing = std.testing;
impl: Impl = .{},
pub const Impl = if (std.builtin.single_threaded)
DebugEvent
else
AtomicEvent;
/// Sets the event if not already set and wakes up all the threads waiting on
/// the event. It is safe to call `set` multiple times before calling `wait`.
/// However it is illegal to call `set` after `wait` is called until the event
/// is `reset`. This function is thread-safe.
pub fn set(ev: *StaticResetEvent) void {
return ev.impl.set();
}
/// Wait for the event to be set by blocking the current thread.
/// Thread-safe. No spurious wakeups.
/// Upon return from `wait`, the only function available to be called
/// in `StaticResetEvent` is `reset`.
pub fn wait(ev: *StaticResetEvent) void {
return ev.impl.wait();
}
/// Resets the event to its original, unset state.
/// This function is *not* thread-safe. It is equivalent to calling
/// `deinit` followed by `init` but without the possibility of failure.
pub fn reset(ev: *StaticResetEvent) void {
return ev.impl.reset();
}
pub const TimedWaitResult = std.Thread.ResetEvent.TimedWaitResult;
/// Wait for the event to be set by blocking the current thread.
/// A timeout in nanoseconds can be provided as a hint for how
/// long the thread should block on the unset event before returning
/// `TimedWaitResult.timed_out`.
/// Thread-safe. No precision of timing is guaranteed.
/// Upon return from `timedWait`, the only function available to be called
/// in `StaticResetEvent` is `reset`.
pub fn timedWait(ev: *StaticResetEvent, timeout_ns: u64) TimedWaitResult {
return ev.impl.timedWait(timeout_ns);
}
/// For single-threaded builds, we use this to detect deadlocks.
/// In unsafe modes this ends up being no-ops.
pub const DebugEvent = struct {
state: State = State.unset,
const State = enum {
unset,
set,
waited,
};
/// This function is provided so that this type can be re-used inside
/// `std.Thread.ResetEvent`.
pub fn init(ev: *DebugEvent) void {
ev.* = .{};
}
/// This function is provided so that this type can be re-used inside
/// `std.Thread.ResetEvent`.
pub fn deinit(ev: *DebugEvent) void {
ev.* = undefined;
}
pub fn set(ev: *DebugEvent) void {
switch (ev.state) {
.unset => ev.state = .set,
.set => {},
.waited => unreachable, // Not allowed to call `set` until `reset`.
}
}
pub fn wait(ev: *DebugEvent) void {
switch (ev.state) {
.unset => unreachable, // Deadlock detected.
.set => return,
.waited => unreachable, // Not allowed to call `wait` until `reset`.
}
}
pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult {
_ = timeout;
switch (ev.state) {
.unset => return .timed_out,
.set => return .event_set,
.waited => unreachable, // Not allowed to call `wait` until `reset`.
}
}
pub fn reset(ev: *DebugEvent) void {
ev.state = .unset;
}
};
pub const AtomicEvent = struct {
waiters: u32 = 0,
const WAKE = 1 << 0;
const WAIT = 1 << 1;
/// This function is provided so that this type can be re-used inside
/// `std.Thread.ResetEvent`.
pub fn init(ev: *AtomicEvent) void {
ev.* = .{};
}
/// This function is provided so that this type can be re-used inside
/// `std.Thread.ResetEvent`.
pub fn deinit(ev: *AtomicEvent) void {
ev.* = undefined;
}
pub fn set(ev: *AtomicEvent) void {
const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release);
if (waiters >= WAIT) {
return Futex.wake(&ev.waiters, waiters >> 1);
}
}
pub fn wait(ev: *AtomicEvent) void {
switch (ev.timedWait(null)) {
.timed_out => unreachable,
.event_set => return,
}
}
pub fn timedWait(ev: *AtomicEvent, timeout: ?u64) TimedWaitResult {
var waiters = @atomicLoad(u32, &ev.waiters, .Acquire);
while (waiters != WAKE) {
waiters = @cmpxchgWeak(u32, &ev.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse {
if (Futex.wait(&ev.waiters, timeout)) |_| {
return .event_set;
} else |_| {
return .timed_out;
}
};
}
return .event_set;
}
pub fn reset(ev: *AtomicEvent) void {
@atomicStore(u32, &ev.waiters, 0, .Monotonic);
}
pub const Futex = switch (std.Target.current.os.tag) {
.windows => WindowsFutex,
.linux => LinuxFutex,
else => SpinFutex,
};
pub const SpinFutex = struct {
fn wake(waiters: *u32, wake_count: u32) void {
_ = waiters;
_ = wake_count;
}
fn wait(waiters: *u32, timeout: ?u64) !void {
var timer: time.Timer = undefined;
if (timeout != null)
timer = time.Timer.start() catch return error.TimedOut;
while (@atomicLoad(u32, waiters, .Acquire) != WAKE) {
std.os.sched_yield() catch std.atomic.spinLoopHint();
if (timeout) |timeout_ns| {
if (timer.read() >= timeout_ns)
return error.TimedOut;
}
}
}
};
pub const LinuxFutex = struct {
fn wake(waiters: *u32, wake_count: u32) void {
_ = wake_count;
const waiting = std.math.maxInt(i32); // wake_count
const ptr = @ptrCast(*const i32, waiters);
const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting);
assert(linux.getErrno(rc) == .SUCCESS);
}
fn wait(waiters: *u32, timeout: ?u64) !void {
var ts: linux.timespec = undefined;
var ts_ptr: ?*linux.timespec = null;
if (timeout) |timeout_ns| {
ts_ptr = &ts;
ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s);
ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s);
}
while (true) {
const waiting = @atomicLoad(u32, waiters, .Acquire);
if (waiting == WAKE)
return;
const expected = @intCast(i32, waiting);
const ptr = @ptrCast(*const i32, waiters);
const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr);
switch (linux.getErrno(rc)) {
.SUCCESS => continue,
.TIMEDOUT => return error.TimedOut,
.INTR => continue,
.AGAIN => return,
else => unreachable,
}
}
}
};
pub const WindowsFutex = struct {
pub fn wake(waiters: *u32, wake_count: u32) void {
const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count);
const key = @ptrCast(*const c_void, waiters);
var waiting = wake_count;
while (waiting != 0) : (waiting -= 1) {
const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
assert(rc == .SUCCESS);
}
}
pub fn wait(waiters: *u32, timeout: ?u64) !void {
const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout);
const key = @ptrCast(*const c_void, waiters);
// NT uses timeouts in units of 100ns with negative value being relative
var timeout_ptr: ?*windows.LARGE_INTEGER = null;
var timeout_value: windows.LARGE_INTEGER = undefined;
if (timeout) |timeout_ns| {
timeout_ptr = &timeout_value;
timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100);
}
// NtWaitForKeyedEvent doesnt have spurious wake-ups
var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr);
switch (rc) {
.TIMEOUT => {
// update the wait count to signal that we're not waiting anymore.
// if the .set() thread already observed that we are, perform a
// matching NtWaitForKeyedEvent so that the .set() thread doesn't
// deadlock trying to run NtReleaseKeyedEvent above.
var waiting = @atomicLoad(u32, waiters, .Monotonic);
while (true) {
if (waiting == WAKE) {
rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
assert(rc == windows.NTSTATUS.WAIT_0);
break;
} else {
waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break;
continue;
}
}
return error.TimedOut;
},
windows.NTSTATUS.WAIT_0 => {},
else => unreachable,
}
}
var event_handle: usize = EMPTY;
const EMPTY = ~@as(usize, 0);
const LOADING = EMPTY - 1;
pub fn getEventHandle() ?windows.HANDLE {
var handle = @atomicLoad(usize, &event_handle, .Monotonic);
while (true) {
switch (handle) {
EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse {
const handle_ptr = @ptrCast(*windows.HANDLE, &handle);
const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE;
if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS)
handle = 0;
@atomicStore(usize, &event_handle, handle, .Monotonic);
return @intToPtr(?windows.HANDLE, handle);
},
LOADING => {
std.os.sched_yield() catch std.atomic.spinLoopHint();
handle = @atomicLoad(usize, &event_handle, .Monotonic);
},
else => {
return @intToPtr(?windows.HANDLE, handle);
},
}
}
}
};
};
test "basic usage" {
var event = StaticResetEvent{};
// test event setting
event.set();
// test event resetting
event.reset();
// test event waiting (non-blocking)
event.set();
event.wait();
event.reset();
event.set();
try testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1));
// test cross-thread signaling
if (std.builtin.single_threaded)
return;
const Context = struct {
const Self = @This();
value: u128 = 0,
in: StaticResetEvent = .{},
out: StaticResetEvent = .{},
fn sender(self: *Self) !void {
// update value and signal input
try testing.expect(self.value == 0);
self.value = 1;
self.in.set();
// wait for receiver to update value and signal output
self.out.wait();
try testing.expect(self.value == 2);
// update value and signal final input
self.value = 3;
self.in.set();
}
fn receiver(self: *Self) !void {
// wait for sender to update value and signal input
self.in.wait();
try testing.expect(self.value == 1);
// update value and signal output
self.in.reset();
self.value = 2;
self.out.set();
// wait for sender to update value and signal final input
self.in.wait();
try testing.expect(self.value == 3);
}
fn sleeper(self: *Self) void {
self.in.set();
time.sleep(time.ns_per_ms * 2);
self.value = 5;
self.out.set();
}
fn timedWaiter(self: *Self) !void {
self.in.wait();
try testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us));
try self.out.timedWait(time.ns_per_ms * 100);
try testing.expect(self.value == 5);
}
};
var context = Context{};
const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context});
defer receiver.join();
try context.sender();
if (false) {
// I have now observed this fail on macOS, Windows, and Linux.
// https://github.com/ziglang/zig/issues/7009
var timed = Context.init();
defer timed.deinit();
const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed});
defer sleeper.join();
try timed.timedWaiter();
}
}