mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
Merge pull request #7519 from ziglang/more-pthreads-integration
More pthreads integration
This commit is contained in:
commit
0fd68f49e2
@ -410,11 +410,12 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/bits.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/ResetEvent.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/StaticResetEvent.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/process.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/rand.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/sort.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/addXf3.zig"
|
||||
@ -512,7 +513,6 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/Cache.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/Compilation.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/DepTokenizer.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/Event.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/Module.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/Package.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
|
||||
|
||||
@ -160,6 +160,9 @@ pub fn maybeRefresh(self: *Progress) void {
|
||||
if (now < self.initial_delay_ns) return;
|
||||
const held = self.update_lock.tryAcquire() orelse return;
|
||||
defer held.release();
|
||||
// TODO I have observed this to happen sometimes. I think we need to follow Rust's
|
||||
// lead and guarantee monotonically increasing times in the std lib itself.
|
||||
if (now < self.prev_refresh_timestamp) return;
|
||||
if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
|
||||
return self.refreshWithHeldLock();
|
||||
}
|
||||
|
||||
297
lib/std/ResetEvent.zig
Normal file
297
lib/std/ResetEvent.zig
Normal file
@ -0,0 +1,297 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2015-2020 Zig Contributors
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.
|
||||
|
||||
//! A thread-safe resource which supports blocking until signaled.
|
||||
//! This API is for kernel threads, not evented I/O.
|
||||
//! This API requires being initialized at runtime, and initialization
|
||||
//! can fail. Once initialized, the core operations cannot fail.
|
||||
//! If you need an abstraction that cannot fail to be initialized, see
|
||||
//! `std.StaticResetEvent`. However if you can handle initialization failure,
|
||||
//! it is preferred to use `ResetEvent`.
|
||||
|
||||
const ResetEvent = @This();
|
||||
const std = @import("std.zig");
|
||||
const builtin = std.builtin;
|
||||
const testing = std.testing;
|
||||
const assert = std.debug.assert;
|
||||
const c = std.c;
|
||||
const os = std.os;
|
||||
const time = std.time;
|
||||
|
||||
impl: Impl,
|
||||
|
||||
pub const Impl = if (builtin.single_threaded)
|
||||
std.StaticResetEvent.DebugEvent
|
||||
else if (std.Target.current.isDarwin())
|
||||
DarwinEvent
|
||||
else if (std.Thread.use_pthreads)
|
||||
PosixEvent
|
||||
else
|
||||
std.StaticResetEvent.AtomicEvent;
|
||||
|
||||
pub const InitError = error{SystemResources};
|
||||
|
||||
/// After `init`, it is legal to call any other function.
|
||||
pub fn init(ev: *ResetEvent) InitError!void {
|
||||
return ev.impl.init();
|
||||
}
|
||||
|
||||
/// This function is not thread-safe.
|
||||
/// After `deinit`, the only legal function to call is `init`.
|
||||
pub fn deinit(ev: *ResetEvent) void {
|
||||
return ev.impl.deinit();
|
||||
}
|
||||
|
||||
/// 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: *ResetEvent) void {
|
||||
return ev.impl.set();
|
||||
}
|
||||
|
||||
/// 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: *ResetEvent) void {
|
||||
return ev.impl.reset();
|
||||
}
|
||||
|
||||
/// Wait for the event to be set by blocking the current thread.
|
||||
/// Thread-safe. No spurious wakeups.
|
||||
/// Upon return from `wait`, the only functions available to be called
|
||||
/// in `ResetEvent` are `reset` and `deinit`.
|
||||
pub fn wait(ev: *ResetEvent) void {
|
||||
return ev.impl.wait();
|
||||
}
|
||||
|
||||
pub const TimedWaitResult = enum { event_set, timed_out };
|
||||
|
||||
/// 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 `wait`, the only functions available to be called
|
||||
/// in `ResetEvent` are `reset` and `deinit`.
|
||||
pub fn timedWait(ev: *ResetEvent, timeout_ns: u64) TimedWaitResult {
|
||||
return ev.impl.timedWait(timeout_ns);
|
||||
}
|
||||
|
||||
/// Apple has decided to not support POSIX semaphores, so we go with a
|
||||
/// different approach using Grand Central Dispatch. This API is exposed
|
||||
/// by libSystem so it is guaranteed to be available on all Darwin platforms.
|
||||
pub const DarwinEvent = struct {
|
||||
sem: c.dispatch_semaphore_t = undefined,
|
||||
|
||||
pub fn init(ev: *DarwinEvent) !void {
|
||||
ev.* = .{
|
||||
.sem = c.dispatch_semaphore_create(0) orelse return error.SystemResources,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(ev: *DarwinEvent) void {
|
||||
c.dispatch_release(ev.sem);
|
||||
ev.* = undefined;
|
||||
}
|
||||
|
||||
pub fn set(ev: *DarwinEvent) void {
|
||||
// Empirically this returns the numerical value of the semaphore.
|
||||
_ = c.dispatch_semaphore_signal(ev.sem);
|
||||
}
|
||||
|
||||
pub fn wait(ev: *DarwinEvent) void {
|
||||
assert(c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_FOREVER) == 0);
|
||||
}
|
||||
|
||||
pub fn timedWait(ev: *DarwinEvent, timeout_ns: u64) TimedWaitResult {
|
||||
const t = c.dispatch_time(c.DISPATCH_TIME_NOW, @intCast(i64, timeout_ns));
|
||||
if (c.dispatch_semaphore_wait(ev.sem, t) != 0) {
|
||||
return .timed_out;
|
||||
} else {
|
||||
return .event_set;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(ev: *DarwinEvent) void {
|
||||
// Keep calling until the semaphore goes back down to 0.
|
||||
while (c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_NOW) == 0) {}
|
||||
}
|
||||
};
|
||||
|
||||
/// POSIX semaphores must be initialized at runtime because they are allowed to
|
||||
/// be implemented as file descriptors, in which case initialization would require
|
||||
/// a syscall to open the fd.
|
||||
pub const PosixEvent = struct {
|
||||
sem: c.sem_t = undefined,
|
||||
|
||||
pub fn init(ev: *PosixEvent) !void {
|
||||
switch (c.getErrno(c.sem_init(&ev.sem, 0, 0))) {
|
||||
0 => return,
|
||||
else => return error.SystemResources,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(ev: *PosixEvent) void {
|
||||
assert(c.sem_destroy(&ev.sem) == 0);
|
||||
ev.* = undefined;
|
||||
}
|
||||
|
||||
pub fn set(ev: *PosixEvent) void {
|
||||
assert(c.sem_post(&ev.sem) == 0);
|
||||
}
|
||||
|
||||
pub fn wait(ev: *PosixEvent) void {
|
||||
while (true) {
|
||||
switch (c.getErrno(c.sem_wait(&ev.sem))) {
|
||||
0 => return,
|
||||
c.EINTR => continue,
|
||||
c.EINVAL => unreachable,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timedWait(ev: *PosixEvent, timeout_ns: u64) TimedWaitResult {
|
||||
var ts: os.timespec = undefined;
|
||||
var timeout_abs = timeout_ns;
|
||||
os.clock_gettime(os.CLOCK_REALTIME, &ts) catch return .timed_out;
|
||||
timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s;
|
||||
timeout_abs += @intCast(u64, ts.tv_nsec);
|
||||
ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s));
|
||||
ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s));
|
||||
while (true) {
|
||||
switch (c.getErrno(c.sem_timedwait(&ev.sem, &ts))) {
|
||||
0 => return .event_set,
|
||||
c.EINTR => continue,
|
||||
c.EINVAL => unreachable,
|
||||
c.ETIMEDOUT => return .timed_out,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(ev: *PosixEvent) void {
|
||||
while (true) {
|
||||
switch (c.getErrno(c.sem_trywait(&ev.sem))) {
|
||||
0 => continue, // Need to make it go to zero.
|
||||
c.EINTR => continue,
|
||||
c.EINVAL => unreachable,
|
||||
c.EAGAIN => return, // The semaphore currently has the value zero.
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test "basic usage" {
|
||||
var event: ResetEvent = undefined;
|
||||
try event.init();
|
||||
defer event.deinit();
|
||||
|
||||
// test event setting
|
||||
event.set();
|
||||
|
||||
// test event resetting
|
||||
event.reset();
|
||||
|
||||
// test event waiting (non-blocking)
|
||||
event.set();
|
||||
event.wait();
|
||||
event.reset();
|
||||
|
||||
event.set();
|
||||
testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1));
|
||||
|
||||
// test cross-thread signaling
|
||||
if (builtin.single_threaded)
|
||||
return;
|
||||
|
||||
const Context = struct {
|
||||
const Self = @This();
|
||||
|
||||
value: u128,
|
||||
in: ResetEvent,
|
||||
out: ResetEvent,
|
||||
|
||||
fn init(self: *Self) !void {
|
||||
self.* = .{
|
||||
.value = 0,
|
||||
.in = undefined,
|
||||
.out = undefined,
|
||||
};
|
||||
try self.in.init();
|
||||
try self.out.init();
|
||||
}
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
self.in.deinit();
|
||||
self.out.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn sender(self: *Self) void {
|
||||
// update value and signal input
|
||||
testing.expect(self.value == 0);
|
||||
self.value = 1;
|
||||
self.in.set();
|
||||
|
||||
// wait for receiver to update value and signal output
|
||||
self.out.wait();
|
||||
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();
|
||||
assert(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();
|
||||
assert(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();
|
||||
testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us));
|
||||
try self.out.timedWait(time.ns_per_ms * 100);
|
||||
testing.expect(self.value == 5);
|
||||
}
|
||||
};
|
||||
|
||||
var context: Context = undefined;
|
||||
try context.init();
|
||||
defer context.deinit();
|
||||
const receiver = try std.Thread.spawn(&context, Context.receiver);
|
||||
defer receiver.wait();
|
||||
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(&timed, Context.sleeper);
|
||||
defer sleeper.wait();
|
||||
try timed.timedWaiter();
|
||||
}
|
||||
}
|
||||
@ -3,240 +3,184 @@
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.
|
||||
|
||||
//! 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.ResetEvent` when possible, and use `StaticResetEvent` when
|
||||
//! the logic needs stronger API guarantees.
|
||||
|
||||
const std = @import("std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const testing = std.testing;
|
||||
const StaticResetEvent = @This();
|
||||
const SpinLock = std.SpinLock;
|
||||
const assert = std.debug.assert;
|
||||
const c = std.c;
|
||||
const os = std.os;
|
||||
const time = std.time;
|
||||
const linux = os.linux;
|
||||
const windows = os.windows;
|
||||
const linux = std.os.linux;
|
||||
const windows = std.os.windows;
|
||||
const testing = std.testing;
|
||||
|
||||
/// A resource object which supports blocking until signaled.
|
||||
/// Once finished, the `deinit()` method should be called for correctness.
|
||||
pub const ResetEvent = struct {
|
||||
os_event: OsEvent,
|
||||
impl: Impl = .{},
|
||||
|
||||
pub const OsEvent = if (builtin.single_threaded)
|
||||
DebugEvent
|
||||
else if (builtin.link_libc and builtin.os.tag != .windows and builtin.os.tag != .linux)
|
||||
PosixEvent
|
||||
else
|
||||
AtomicEvent;
|
||||
pub const Impl = if (std.builtin.single_threaded)
|
||||
DebugEvent
|
||||
else
|
||||
AtomicEvent;
|
||||
|
||||
pub fn init() ResetEvent {
|
||||
return ResetEvent{ .os_event = OsEvent.init() };
|
||||
/// 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.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.ResetEvent`.
|
||||
pub fn init(ev: *DebugEvent) void {
|
||||
ev.* = .{};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *ResetEvent) void {
|
||||
self.os_event.deinit();
|
||||
/// This function is provided so that this type can be re-used inside
|
||||
/// `std.ResetEvent`.
|
||||
pub fn deinit(ev: *DebugEvent) void {
|
||||
ev.* = undefined;
|
||||
}
|
||||
|
||||
/// Returns whether or not the event is currenetly set
|
||||
pub fn isSet(self: *ResetEvent) bool {
|
||||
return self.os_event.isSet();
|
||||
}
|
||||
|
||||
/// Sets the event if not already set and
|
||||
/// wakes up all the threads waiting on the event.
|
||||
pub fn set(self: *ResetEvent) void {
|
||||
return self.os_event.set();
|
||||
}
|
||||
|
||||
/// Resets the event to its original, unset state.
|
||||
pub fn reset(self: *ResetEvent) void {
|
||||
return self.os_event.reset();
|
||||
}
|
||||
|
||||
/// Wait for the event to be set by blocking the current thread.
|
||||
pub fn wait(self: *ResetEvent) void {
|
||||
return self.os_event.wait(null) catch unreachable;
|
||||
}
|
||||
|
||||
/// 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 throwing error.TimedOut.
|
||||
pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void {
|
||||
return self.os_event.wait(timeout_ns);
|
||||
}
|
||||
};
|
||||
|
||||
const DebugEvent = struct {
|
||||
is_set: bool,
|
||||
|
||||
fn init() DebugEvent {
|
||||
return DebugEvent{ .is_set = false };
|
||||
}
|
||||
|
||||
fn deinit(self: *DebugEvent) void {
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn isSet(self: *DebugEvent) bool {
|
||||
return self.is_set;
|
||||
}
|
||||
|
||||
fn reset(self: *DebugEvent) void {
|
||||
self.is_set = false;
|
||||
}
|
||||
|
||||
fn set(self: *DebugEvent) void {
|
||||
self.is_set = true;
|
||||
}
|
||||
|
||||
fn wait(self: *DebugEvent, timeout: ?u64) !void {
|
||||
if (self.is_set)
|
||||
return;
|
||||
if (timeout != null)
|
||||
return error.TimedOut;
|
||||
@panic("deadlock detected");
|
||||
}
|
||||
};
|
||||
|
||||
const PosixEvent = struct {
|
||||
is_set: bool,
|
||||
cond: c.pthread_cond_t,
|
||||
mutex: c.pthread_mutex_t,
|
||||
|
||||
fn init() PosixEvent {
|
||||
return PosixEvent{
|
||||
.is_set = false,
|
||||
.cond = c.PTHREAD_COND_INITIALIZER,
|
||||
.mutex = c.PTHREAD_MUTEX_INITIALIZER,
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: *PosixEvent) void {
|
||||
// on dragonfly or openbsd, *destroy() functions can return EINVAL
|
||||
// for statically initialized pthread structures
|
||||
const err = if (builtin.os.tag == .dragonfly or builtin.os.tag == .openbsd)
|
||||
os.EINVAL
|
||||
else
|
||||
0;
|
||||
|
||||
const retm = c.pthread_mutex_destroy(&self.mutex);
|
||||
assert(retm == 0 or retm == err);
|
||||
const retc = c.pthread_cond_destroy(&self.cond);
|
||||
assert(retc == 0 or retc == err);
|
||||
}
|
||||
|
||||
fn isSet(self: *PosixEvent) bool {
|
||||
assert(c.pthread_mutex_lock(&self.mutex) == 0);
|
||||
defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
|
||||
|
||||
return self.is_set;
|
||||
}
|
||||
|
||||
fn reset(self: *PosixEvent) void {
|
||||
assert(c.pthread_mutex_lock(&self.mutex) == 0);
|
||||
defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
|
||||
|
||||
self.is_set = false;
|
||||
}
|
||||
|
||||
fn set(self: *PosixEvent) void {
|
||||
assert(c.pthread_mutex_lock(&self.mutex) == 0);
|
||||
defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
|
||||
|
||||
if (!self.is_set) {
|
||||
self.is_set = true;
|
||||
assert(c.pthread_cond_broadcast(&self.cond) == 0);
|
||||
pub fn set(ev: *DebugEvent) void {
|
||||
switch (ev.state) {
|
||||
.unset => ev.state = .set,
|
||||
.set => {},
|
||||
.waited => unreachable, // Not allowed to call `set` until `reset`.
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(self: *PosixEvent, timeout: ?u64) !void {
|
||||
assert(c.pthread_mutex_lock(&self.mutex) == 0);
|
||||
defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
|
||||
|
||||
// quick guard before possibly calling time syscalls below
|
||||
if (self.is_set)
|
||||
return;
|
||||
|
||||
var ts: os.timespec = undefined;
|
||||
if (timeout) |timeout_ns| {
|
||||
var timeout_abs = timeout_ns;
|
||||
if (comptime std.Target.current.isDarwin()) {
|
||||
var tv: os.darwin.timeval = undefined;
|
||||
assert(os.darwin.gettimeofday(&tv, null) == 0);
|
||||
timeout_abs += @intCast(u64, tv.tv_sec) * time.ns_per_s;
|
||||
timeout_abs += @intCast(u64, tv.tv_usec) * time.ns_per_us;
|
||||
} else {
|
||||
os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable;
|
||||
timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s;
|
||||
timeout_abs += @intCast(u64, ts.tv_nsec);
|
||||
}
|
||||
ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s));
|
||||
ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s));
|
||||
pub fn wait(ev: *DebugEvent) void {
|
||||
switch (ev.state) {
|
||||
.unset => unreachable, // Deadlock detected.
|
||||
.set => return,
|
||||
.waited => unreachable, // Not allowed to call `wait` until `reset`.
|
||||
}
|
||||
}
|
||||
|
||||
while (!self.is_set) {
|
||||
const rc = switch (timeout == null) {
|
||||
true => c.pthread_cond_wait(&self.cond, &self.mutex),
|
||||
else => c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts),
|
||||
};
|
||||
switch (rc) {
|
||||
0 => {},
|
||||
os.ETIMEDOUT => return error.TimedOut,
|
||||
os.EINVAL => unreachable,
|
||||
os.EPERM => unreachable,
|
||||
else => unreachable,
|
||||
}
|
||||
pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const AtomicEvent = struct {
|
||||
waiters: u32,
|
||||
pub const AtomicEvent = struct {
|
||||
waiters: u32 = 0,
|
||||
|
||||
const WAKE = 1 << 0;
|
||||
const WAIT = 1 << 1;
|
||||
|
||||
fn init() AtomicEvent {
|
||||
return AtomicEvent{ .waiters = 0 };
|
||||
/// This function is provided so that this type can be re-used inside
|
||||
/// `std.ResetEvent`.
|
||||
pub fn init(ev: *AtomicEvent) void {
|
||||
ev.* = .{};
|
||||
}
|
||||
|
||||
fn deinit(self: *AtomicEvent) void {
|
||||
self.* = undefined;
|
||||
/// This function is provided so that this type can be re-used inside
|
||||
/// `std.ResetEvent`.
|
||||
pub fn deinit(ev: *AtomicEvent) void {
|
||||
ev.* = undefined;
|
||||
}
|
||||
|
||||
fn isSet(self: *const AtomicEvent) bool {
|
||||
return @atomicLoad(u32, &self.waiters, .Acquire) == WAKE;
|
||||
}
|
||||
|
||||
fn reset(self: *AtomicEvent) void {
|
||||
@atomicStore(u32, &self.waiters, 0, .Monotonic);
|
||||
}
|
||||
|
||||
fn set(self: *AtomicEvent) void {
|
||||
const waiters = @atomicRmw(u32, &self.waiters, .Xchg, WAKE, .Release);
|
||||
pub fn set(ev: *AtomicEvent) void {
|
||||
const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release);
|
||||
if (waiters >= WAIT) {
|
||||
return Futex.wake(&self.waiters, waiters >> 1);
|
||||
return Futex.wake(&ev.waiters, waiters >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(self: *AtomicEvent, timeout: ?u64) !void {
|
||||
var waiters = @atomicLoad(u32, &self.waiters, .Acquire);
|
||||
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, &self.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse return Futex.wait(&self.waiters, timeout);
|
||||
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 const Futex = switch (builtin.os.tag) {
|
||||
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,
|
||||
};
|
||||
|
||||
const SpinFutex = struct {
|
||||
pub const SpinFutex = struct {
|
||||
fn wake(waiters: *u32, wake_count: u32) void {}
|
||||
|
||||
fn wait(waiters: *u32, timeout: ?u64) !void {
|
||||
// TODO: handle platforms where a monotonic timer isnt available
|
||||
var timer: time.Timer = undefined;
|
||||
if (timeout != null)
|
||||
timer = time.Timer.start() catch unreachable;
|
||||
timer = time.Timer.start() catch return error.TimedOut;
|
||||
|
||||
while (@atomicLoad(u32, waiters, .Acquire) != WAKE) {
|
||||
SpinLock.yield();
|
||||
@ -248,7 +192,7 @@ const AtomicEvent = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const LinuxFutex = struct {
|
||||
pub const LinuxFutex = struct {
|
||||
fn wake(waiters: *u32, wake_count: u32) void {
|
||||
const waiting = std.math.maxInt(i32); // wake_count
|
||||
const ptr = @ptrCast(*const i32, waiters);
|
||||
@ -283,7 +227,7 @@ const AtomicEvent = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const WindowsFutex = struct {
|
||||
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);
|
||||
@ -362,48 +306,33 @@ const AtomicEvent = struct {
|
||||
};
|
||||
};
|
||||
|
||||
test "ResetEvent" {
|
||||
var event = ResetEvent.init();
|
||||
defer event.deinit();
|
||||
test "basic usage" {
|
||||
var event = StaticResetEvent{};
|
||||
|
||||
// test event setting
|
||||
testing.expect(event.isSet() == false);
|
||||
event.set();
|
||||
testing.expect(event.isSet() == true);
|
||||
|
||||
// test event resetting
|
||||
event.reset();
|
||||
testing.expect(event.isSet() == false);
|
||||
|
||||
// test event waiting (non-blocking)
|
||||
event.set();
|
||||
event.wait();
|
||||
try event.timedWait(1);
|
||||
event.reset();
|
||||
|
||||
event.set();
|
||||
testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1));
|
||||
|
||||
// test cross-thread signaling
|
||||
if (builtin.single_threaded)
|
||||
if (std.builtin.single_threaded)
|
||||
return;
|
||||
|
||||
const Context = struct {
|
||||
const Self = @This();
|
||||
|
||||
value: u128,
|
||||
in: ResetEvent,
|
||||
out: ResetEvent,
|
||||
|
||||
fn init() Self {
|
||||
return Self{
|
||||
.value = 0,
|
||||
.in = ResetEvent.init(),
|
||||
.out = ResetEvent.init(),
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
self.in.deinit();
|
||||
self.out.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
value: u128 = 0,
|
||||
in: StaticResetEvent = .{},
|
||||
out: StaticResetEvent = .{},
|
||||
|
||||
fn sender(self: *Self) void {
|
||||
// update value and signal input
|
||||
@ -444,14 +373,13 @@ test "ResetEvent" {
|
||||
|
||||
fn timedWaiter(self: *Self) !void {
|
||||
self.in.wait();
|
||||
testing.expectError(error.TimedOut, self.out.timedWait(time.ns_per_us));
|
||||
testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us));
|
||||
try self.out.timedWait(time.ns_per_ms * 100);
|
||||
testing.expect(self.value == 5);
|
||||
}
|
||||
};
|
||||
|
||||
var context = Context.init();
|
||||
defer context.deinit();
|
||||
var context = Context{};
|
||||
const receiver = try std.Thread.spawn(&context, Context.receiver);
|
||||
defer receiver.wait();
|
||||
context.sender();
|
||||
@ -7,14 +7,15 @@ const std = @import("std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const testing = std.testing;
|
||||
const assert = std.debug.assert;
|
||||
const StaticResetEvent = std.StaticResetEvent;
|
||||
|
||||
/// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`.
|
||||
/// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like).
|
||||
/// Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`.
|
||||
/// Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like).
|
||||
pub const AutoResetEvent = struct {
|
||||
/// AutoResetEvent has 3 possible states:
|
||||
/// - UNSET: the AutoResetEvent is currently unset
|
||||
/// - SET: the AutoResetEvent was notified before a wait() was called
|
||||
/// - <std.ResetEvent pointer>: there is an active waiter waiting for a notification.
|
||||
/// - <StaticResetEvent pointer>: there is an active waiter waiting for a notification.
|
||||
///
|
||||
/// When attempting to wait:
|
||||
/// if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
|
||||
@ -25,20 +26,20 @@ pub const AutoResetEvent = struct {
|
||||
/// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
|
||||
///
|
||||
/// This ensures that the event is automatically reset after a wait() has been issued
|
||||
/// and avoids the race condition when using std.ResetEvent in the following scenario:
|
||||
/// thread 1 | thread 2
|
||||
/// std.ResetEvent.wait() |
|
||||
/// | std.ResetEvent.set()
|
||||
/// | std.ResetEvent.set()
|
||||
/// std.ResetEvent.reset() |
|
||||
/// std.ResetEvent.wait() | (missed the second .set() notification above)
|
||||
/// and avoids the race condition when using StaticResetEvent in the following scenario:
|
||||
/// thread 1 | thread 2
|
||||
/// StaticResetEvent.wait() |
|
||||
/// | StaticResetEvent.set()
|
||||
/// | StaticResetEvent.set()
|
||||
/// StaticResetEvent.reset() |
|
||||
/// StaticResetEvent.wait() | (missed the second .set() notification above)
|
||||
state: usize = UNSET,
|
||||
|
||||
const UNSET = 0;
|
||||
const SET = 1;
|
||||
|
||||
/// the minimum alignment for the `*std.ResetEvent` created by wait*()
|
||||
const event_align = std.math.max(@alignOf(std.ResetEvent), 2);
|
||||
/// the minimum alignment for the `*StaticResetEvent` created by wait*()
|
||||
const event_align = std.math.max(@alignOf(StaticResetEvent), 2);
|
||||
|
||||
pub fn wait(self: *AutoResetEvent) void {
|
||||
self.waitFor(null) catch unreachable;
|
||||
@ -49,12 +50,9 @@ pub const AutoResetEvent = struct {
|
||||
}
|
||||
|
||||
fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void {
|
||||
// lazily initialized std.ResetEvent
|
||||
var reset_event: std.ResetEvent align(event_align) = undefined;
|
||||
// lazily initialized StaticResetEvent
|
||||
var reset_event: StaticResetEvent align(event_align) = undefined;
|
||||
var has_reset_event = false;
|
||||
defer if (has_reset_event) {
|
||||
reset_event.deinit();
|
||||
};
|
||||
|
||||
var state = @atomicLoad(usize, &self.state, .SeqCst);
|
||||
while (true) {
|
||||
@ -72,7 +70,7 @@ pub const AutoResetEvent = struct {
|
||||
// lazily initialize the ResetEvent if it hasn't been already
|
||||
if (!has_reset_event) {
|
||||
has_reset_event = true;
|
||||
reset_event = std.ResetEvent.init();
|
||||
reset_event = .{};
|
||||
}
|
||||
|
||||
// Since the AutoResetEvent currently isnt set,
|
||||
@ -97,9 +95,10 @@ pub const AutoResetEvent = struct {
|
||||
};
|
||||
|
||||
// wait with a timeout and return if signalled via set()
|
||||
if (reset_event.timedWait(timeout_ns)) |_| {
|
||||
return;
|
||||
} else |timed_out| {}
|
||||
switch (reset_event.timedWait(timeout_ns)) {
|
||||
.event_set => return,
|
||||
.timed_out => {},
|
||||
}
|
||||
|
||||
// If we timed out, we need to transition the AutoResetEvent back to UNSET.
|
||||
// If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent.
|
||||
@ -164,7 +163,7 @@ pub const AutoResetEvent = struct {
|
||||
continue;
|
||||
}
|
||||
|
||||
const reset_event = @intToPtr(*align(event_align) std.ResetEvent, state);
|
||||
const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state);
|
||||
reset_event.set();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -270,6 +270,13 @@ pub extern "c" fn pthread_atfork(
|
||||
parent: ?fn () callconv(.C) void,
|
||||
child: ?fn () callconv(.C) void,
|
||||
) c_int;
|
||||
pub extern "c" fn sem_init(sem: *sem_t, pshared: c_int, value: c_uint) c_int;
|
||||
pub extern "c" fn sem_destroy(sem: *sem_t) c_int;
|
||||
pub extern "c" fn sem_post(sem: *sem_t) c_int;
|
||||
pub extern "c" fn sem_wait(sem: *sem_t) c_int;
|
||||
pub extern "c" fn sem_trywait(sem: *sem_t) c_int;
|
||||
pub extern "c" fn sem_timedwait(sem: *sem_t, abs_timeout: *const timespec) c_int;
|
||||
pub extern "c" fn sem_getvalue(sem: *sem_t, sval: *c_int) c_int;
|
||||
|
||||
pub extern "c" fn kqueue() c_int;
|
||||
pub extern "c" fn kevent(
|
||||
@ -316,6 +323,7 @@ pub extern "c" fn dn_expand(
|
||||
pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{};
|
||||
pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) c_int;
|
||||
pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) c_int;
|
||||
pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) c_int;
|
||||
pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int;
|
||||
|
||||
pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{};
|
||||
|
||||
@ -177,6 +177,7 @@ pub const pthread_cond_t = extern struct {
|
||||
__sig: c_long = 0x3CB0B1BB,
|
||||
__opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__,
|
||||
};
|
||||
pub const sem_t = c_int;
|
||||
const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40;
|
||||
const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24;
|
||||
|
||||
@ -186,3 +187,15 @@ pub const pthread_attr_t = extern struct {
|
||||
};
|
||||
|
||||
pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;
|
||||
|
||||
// Grand Central Dispatch is exposed by libSystem.
|
||||
pub const dispatch_semaphore_t = *opaque{};
|
||||
pub const dispatch_time_t = u64;
|
||||
pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0);
|
||||
pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0);
|
||||
pub extern "c" fn dispatch_semaphore_create(value: isize) ?dispatch_semaphore_t;
|
||||
pub extern "c" fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) isize;
|
||||
pub extern "c" fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) isize;
|
||||
|
||||
pub extern "c" fn dispatch_release(object: *c_void) void;
|
||||
pub extern "c" fn dispatch_time(when: dispatch_time_t, delta: i64) dispatch_time_t;
|
||||
|
||||
@ -47,6 +47,15 @@ pub const pthread_attr_t = extern struct {
|
||||
__align: c_long,
|
||||
};
|
||||
|
||||
pub const sem_t = extern struct {
|
||||
_magic: u32,
|
||||
_kern: extern struct {
|
||||
_count: u32,
|
||||
_flags: u32,
|
||||
},
|
||||
_padding: u32,
|
||||
};
|
||||
|
||||
pub const EAI = extern enum(c_int) {
|
||||
/// address family for hostname not supported
|
||||
ADDRFAMILY = 1,
|
||||
|
||||
@ -123,6 +123,10 @@ pub const pthread_mutex_t = extern struct {
|
||||
pub const pthread_cond_t = extern struct {
|
||||
size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
|
||||
};
|
||||
pub const sem_t = extern struct {
|
||||
__size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)),
|
||||
};
|
||||
|
||||
const __SIZEOF_PTHREAD_COND_T = 48;
|
||||
const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) {
|
||||
.musl, .musleabi, .musleabihf => if (@sizeOf(usize) == 8) 40 else 24,
|
||||
@ -134,6 +138,7 @@ const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
const __SIZEOF_SEM_T = 4 * @sizeOf(usize);
|
||||
|
||||
pub const RTLD_LAZY = 1;
|
||||
pub const RTLD_NOW = 2;
|
||||
|
||||
@ -274,9 +274,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
|
||||
// and call abort()
|
||||
|
||||
// Sleep forever without hammering the CPU
|
||||
var event = std.ResetEvent.init();
|
||||
var event: std.StaticResetEvent = .{};
|
||||
event.wait();
|
||||
|
||||
unreachable;
|
||||
}
|
||||
},
|
||||
|
||||
@ -758,7 +758,8 @@ test "open file with exclusive lock twice, make sure it waits" {
|
||||
}
|
||||
};
|
||||
|
||||
var evt = std.ResetEvent.init();
|
||||
var evt: std.ResetEvent = undefined;
|
||||
try evt.init();
|
||||
defer evt.deinit();
|
||||
|
||||
const t = try std.Thread.spawn(S.C{ .dir = &tmp.dir, .evt = &evt }, S.checkFn);
|
||||
@ -771,8 +772,6 @@ test "open file with exclusive lock twice, make sure it waits" {
|
||||
std.time.sleep(SLEEP_TIMEOUT_NS);
|
||||
if (timer.read() >= SLEEP_TIMEOUT_NS) break;
|
||||
}
|
||||
// Check that createFile is still waiting for the lock to be released.
|
||||
testing.expect(!evt.isSet());
|
||||
file.close();
|
||||
// No timeout to avoid failures on heavily loaded systems.
|
||||
evt.wait();
|
||||
|
||||
@ -10,7 +10,7 @@ const assert = std.debug.assert;
|
||||
const windows = os.windows;
|
||||
const testing = std.testing;
|
||||
const SpinLock = std.SpinLock;
|
||||
const ResetEvent = std.ResetEvent;
|
||||
const StaticResetEvent = std.StaticResetEvent;
|
||||
|
||||
/// Lock may be held only once. If the same thread tries to acquire
|
||||
/// the same mutex twice, it deadlocks. This type supports static
|
||||
@ -37,6 +37,8 @@ pub const Mutex = if (builtin.single_threaded)
|
||||
Dummy
|
||||
else if (builtin.os.tag == .windows)
|
||||
WindowsMutex
|
||||
else if (std.Thread.use_pthreads)
|
||||
PthreadMutex
|
||||
else if (builtin.link_libc or builtin.os.tag == .linux)
|
||||
// stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs
|
||||
struct {
|
||||
@ -52,7 +54,7 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
|
||||
|
||||
const Node = struct {
|
||||
next: ?*Node,
|
||||
event: ResetEvent,
|
||||
event: StaticResetEvent,
|
||||
};
|
||||
|
||||
pub fn tryAcquire(self: *Mutex) ?Held {
|
||||
@ -88,11 +90,12 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
|
||||
state = @atomicLoad(usize, &self.state, .Monotonic);
|
||||
}
|
||||
|
||||
// create the ResetEvent node on the stack
|
||||
// create the StaticResetEvent node on the stack
|
||||
// (faster than threadlocal on platforms like OSX)
|
||||
var node: Node = undefined;
|
||||
node.event = ResetEvent.init();
|
||||
defer node.event.deinit();
|
||||
var node: Node = .{
|
||||
.next = undefined,
|
||||
.event = .{},
|
||||
};
|
||||
|
||||
// we've spun too long, try and add our node to the LIFO queue.
|
||||
// if the mutex becomes available in the process, try and grab it instead.
|
||||
@ -166,6 +169,52 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
|
||||
else
|
||||
SpinLock;
|
||||
|
||||
pub const PthreadMutex = struct {
|
||||
pthread_mutex: std.c.pthread_mutex_t = init,
|
||||
|
||||
pub const Held = struct {
|
||||
mutex: *PthreadMutex,
|
||||
|
||||
pub fn release(self: Held) void {
|
||||
switch (std.c.pthread_mutex_unlock(&self.mutex.pthread_mutex)) {
|
||||
0 => return,
|
||||
std.c.EINVAL => unreachable,
|
||||
std.c.EAGAIN => unreachable,
|
||||
std.c.EPERM => unreachable,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Create a new mutex in unlocked state.
|
||||
pub const init = std.c.PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/// Try to acquire the mutex without blocking. Returns null if
|
||||
/// the mutex is unavailable. Otherwise returns Held. Call
|
||||
/// release on Held.
|
||||
pub fn tryAcquire(self: *PthreadMutex) ?Held {
|
||||
if (std.c.pthread_mutex_trylock(&self.pthread_mutex) == 0) {
|
||||
return Held{ .mutex = self };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Acquire the mutex. Will deadlock if the mutex is already
|
||||
/// held by the calling thread.
|
||||
pub fn acquire(self: *PthreadMutex) Held {
|
||||
switch (std.c.pthread_mutex_lock(&self.pthread_mutex)) {
|
||||
0 => return Held{ .mutex = self },
|
||||
std.c.EINVAL => unreachable,
|
||||
std.c.EBUSY => unreachable,
|
||||
std.c.EAGAIN => unreachable,
|
||||
std.c.EDEADLK => unreachable,
|
||||
std.c.EPERM => unreachable,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// This has the sematics as `Mutex`, however it does not actually do any
|
||||
/// synchronization. Operations are safety-checked no-ops.
|
||||
pub const Dummy = struct {
|
||||
@ -236,7 +285,7 @@ const WindowsMutex = struct {
|
||||
fn acquireSlow(self: *WindowsMutex) Held {
|
||||
// try to use NT keyed events for blocking, falling back to spinlock if unavailable
|
||||
@setCold(true);
|
||||
const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning();
|
||||
const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return self.acquireSpinning();
|
||||
const key = @ptrCast(*const c_void, &self.state.waiters);
|
||||
|
||||
while (true) : (SpinLock.loopHint(1)) {
|
||||
@ -264,7 +313,7 @@ const WindowsMutex = struct {
|
||||
pub fn release(self: Held) void {
|
||||
// unlock without a rmw/cmpxchg instruction
|
||||
@atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release);
|
||||
const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return;
|
||||
const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return;
|
||||
const key = @ptrCast(*const c_void, &self.mutex.state.waiters);
|
||||
|
||||
while (true) : (SpinLock.loopHint(1)) {
|
||||
|
||||
@ -30,10 +30,11 @@ pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
|
||||
pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
|
||||
pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
|
||||
pub const Progress = @import("Progress.zig");
|
||||
pub const ResetEvent = @import("reset_event.zig").ResetEvent;
|
||||
pub const ResetEvent = @import("ResetEvent.zig");
|
||||
pub const SemanticVersion = @import("SemanticVersion.zig");
|
||||
pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
|
||||
pub const SpinLock = @import("spinlock.zig").SpinLock;
|
||||
pub const StaticResetEvent = @import("StaticResetEvent.zig");
|
||||
pub const StringHashMap = hash_map.StringHashMap;
|
||||
pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;
|
||||
pub const StringArrayHashMap = array_hash_map.StringArrayHashMap;
|
||||
|
||||
@ -135,6 +135,8 @@ emit_docs: ?EmitLoc,
|
||||
|
||||
c_header: ?c_link.Header,
|
||||
|
||||
work_queue_wait_group: WaitGroup,
|
||||
|
||||
pub const InnerError = Module.InnerError;
|
||||
|
||||
pub const CRTFile = struct {
|
||||
@ -1006,11 +1008,15 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
.test_filter = options.test_filter,
|
||||
.test_name_prefix = options.test_name_prefix,
|
||||
.test_evented_io = options.test_evented_io,
|
||||
.work_queue_wait_group = undefined,
|
||||
};
|
||||
break :comp comp;
|
||||
};
|
||||
errdefer comp.destroy();
|
||||
|
||||
try comp.work_queue_wait_group.init();
|
||||
errdefer comp.work_queue_wait_group.deinit();
|
||||
|
||||
if (comp.bin_file.options.module) |mod| {
|
||||
try comp.work_queue.writeItem(.{ .generate_builtin_zig = {} });
|
||||
}
|
||||
@ -1191,6 +1197,8 @@ pub fn destroy(self: *Compilation) void {
|
||||
self.cache_parent.manifest_dir.close();
|
||||
if (self.owned_link_dir) |*dir| dir.close();
|
||||
|
||||
self.work_queue_wait_group.deinit();
|
||||
|
||||
// This destroys `self`.
|
||||
self.arena_state.promote(gpa).deinit();
|
||||
}
|
||||
@ -1405,13 +1413,13 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
var arena = std.heap.ArenaAllocator.init(self.gpa);
|
||||
defer arena.deinit();
|
||||
|
||||
var wg = WaitGroup{};
|
||||
defer wg.wait();
|
||||
self.work_queue_wait_group.reset();
|
||||
defer self.work_queue_wait_group.wait();
|
||||
|
||||
while (self.c_object_work_queue.readItem()) |c_object| {
|
||||
wg.start();
|
||||
self.work_queue_wait_group.start();
|
||||
try self.thread_pool.spawn(workerUpdateCObject, .{
|
||||
self, c_object, &c_comp_progress_node, &wg,
|
||||
self, c_object, &c_comp_progress_node, &self.work_queue_wait_group,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1764,7 +1772,7 @@ fn workerUpdateCObject(
|
||||
progress_node: *std.Progress.Node,
|
||||
wg: *WaitGroup,
|
||||
) void {
|
||||
defer wg.stop();
|
||||
defer wg.finish();
|
||||
|
||||
comp.updateCObject(c_object, progress_node) catch |err| switch (err) {
|
||||
error.AnalysisFail => return,
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2015-2020 Zig Contributors
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.
|
||||
const std = @import("std");
|
||||
const Event = @This();
|
||||
|
||||
lock: std.Mutex = .{},
|
||||
event: std.ResetEvent = undefined,
|
||||
state: enum { empty, waiting, notified } = .empty,
|
||||
|
||||
pub fn wait(self: *Event) void {
|
||||
const held = self.lock.acquire();
|
||||
|
||||
switch (self.state) {
|
||||
.empty => {
|
||||
self.state = .waiting;
|
||||
self.event = @TypeOf(self.event).init();
|
||||
held.release();
|
||||
self.event.wait();
|
||||
self.event.deinit();
|
||||
},
|
||||
.waiting => unreachable,
|
||||
.notified => held.release(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(self: *Event) void {
|
||||
const held = self.lock.acquire();
|
||||
|
||||
switch (self.state) {
|
||||
.empty => {
|
||||
self.state = .notified;
|
||||
held.release();
|
||||
},
|
||||
.waiting => {
|
||||
held.release();
|
||||
self.event.set();
|
||||
},
|
||||
.notified => unreachable,
|
||||
}
|
||||
}
|
||||
@ -9,70 +9,102 @@ const ThreadPool = @This();
|
||||
lock: std.Mutex = .{},
|
||||
is_running: bool = true,
|
||||
allocator: *std.mem.Allocator,
|
||||
running: usize = 0,
|
||||
threads: []*std.Thread,
|
||||
workers: []Worker,
|
||||
run_queue: RunQueue = .{},
|
||||
idle_queue: IdleQueue = .{},
|
||||
|
||||
const IdleQueue = std.SinglyLinkedList(std.AutoResetEvent);
|
||||
const IdleQueue = std.SinglyLinkedList(std.ResetEvent);
|
||||
const RunQueue = std.SinglyLinkedList(Runnable);
|
||||
const Runnable = struct {
|
||||
runFn: fn (*Runnable) void,
|
||||
};
|
||||
|
||||
const Worker = struct {
|
||||
pool: *ThreadPool,
|
||||
thread: *std.Thread,
|
||||
/// The node is for this worker only and must have an already initialized event
|
||||
/// when the thread is spawned.
|
||||
idle_node: IdleQueue.Node,
|
||||
|
||||
fn run(worker: *Worker) void {
|
||||
while (true) {
|
||||
const held = worker.pool.lock.acquire();
|
||||
|
||||
if (worker.pool.run_queue.popFirst()) |run_node| {
|
||||
held.release();
|
||||
(run_node.data.runFn)(&run_node.data);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (worker.pool.is_running) {
|
||||
worker.idle_node.data.reset();
|
||||
|
||||
worker.pool.idle_queue.prepend(&worker.idle_node);
|
||||
held.release();
|
||||
|
||||
worker.idle_node.data.wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
held.release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.threads = &[_]*std.Thread{},
|
||||
.workers = &[_]Worker{},
|
||||
};
|
||||
if (std.builtin.single_threaded)
|
||||
return;
|
||||
|
||||
errdefer self.deinit();
|
||||
const worker_count = std.math.max(1, std.Thread.cpuCount() catch 1);
|
||||
self.workers = try allocator.alloc(Worker, worker_count);
|
||||
errdefer allocator.free(self.workers);
|
||||
|
||||
var num_threads = std.Thread.cpuCount() catch 1;
|
||||
if (num_threads > 0)
|
||||
self.threads = try allocator.alloc(*std.Thread, num_threads);
|
||||
var worker_index: usize = 0;
|
||||
errdefer self.destroyWorkers(worker_index);
|
||||
while (worker_index < worker_count) : (worker_index += 1) {
|
||||
const worker = &self.workers[worker_index];
|
||||
worker.pool = self;
|
||||
|
||||
while (num_threads > 0) : (num_threads -= 1) {
|
||||
const thread = try std.Thread.spawn(self, runWorker);
|
||||
self.threads[self.running] = thread;
|
||||
self.running += 1;
|
||||
// Each worker requires its ResetEvent to be pre-initialized.
|
||||
try worker.idle_node.data.init();
|
||||
errdefer worker.idle_node.data.deinit();
|
||||
|
||||
worker.thread = try std.Thread.spawn(worker, Worker.run);
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyWorkers(self: *ThreadPool, spawned: usize) void {
|
||||
for (self.workers[0..spawned]) |*worker| {
|
||||
worker.thread.wait();
|
||||
worker.idle_node.data.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *ThreadPool) void {
|
||||
self.shutdown();
|
||||
{
|
||||
const held = self.lock.acquire();
|
||||
defer held.release();
|
||||
|
||||
std.debug.assert(!self.is_running);
|
||||
for (self.threads[0..self.running]) |thread|
|
||||
thread.wait();
|
||||
self.is_running = false;
|
||||
while (self.idle_queue.popFirst()) |idle_node|
|
||||
idle_node.data.set();
|
||||
}
|
||||
|
||||
defer self.threads = &[_]*std.Thread{};
|
||||
if (self.running > 0)
|
||||
self.allocator.free(self.threads);
|
||||
}
|
||||
|
||||
pub fn shutdown(self: *ThreadPool) void {
|
||||
const held = self.lock.acquire();
|
||||
|
||||
if (!self.is_running)
|
||||
return held.release();
|
||||
|
||||
var idle_queue = self.idle_queue;
|
||||
self.idle_queue = .{};
|
||||
self.is_running = false;
|
||||
held.release();
|
||||
|
||||
while (idle_queue.popFirst()) |idle_node|
|
||||
idle_node.data.set();
|
||||
self.destroyWorkers(self.workers.len);
|
||||
self.allocator.free(self.workers);
|
||||
}
|
||||
|
||||
pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void {
|
||||
if (std.builtin.single_threaded) {
|
||||
@call(.{}, func, args);
|
||||
const result = @call(.{}, func, args);
|
||||
return;
|
||||
}
|
||||
|
||||
const Args = @TypeOf(args);
|
||||
const Closure = struct {
|
||||
arguments: Args,
|
||||
@ -83,44 +115,24 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void {
|
||||
const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable);
|
||||
const closure = @fieldParentPtr(@This(), "run_node", run_node);
|
||||
const result = @call(.{}, func, closure.arguments);
|
||||
|
||||
const held = closure.pool.lock.acquire();
|
||||
defer held.release();
|
||||
closure.pool.allocator.destroy(closure);
|
||||
}
|
||||
};
|
||||
|
||||
const held = self.lock.acquire();
|
||||
defer held.release();
|
||||
|
||||
const closure = try self.allocator.create(Closure);
|
||||
closure.* = .{
|
||||
.arguments = args,
|
||||
.pool = self,
|
||||
};
|
||||
|
||||
const held = self.lock.acquire();
|
||||
self.run_queue.prepend(&closure.run_node);
|
||||
|
||||
const idle_node = self.idle_queue.popFirst();
|
||||
held.release();
|
||||
|
||||
if (idle_node) |node|
|
||||
node.data.set();
|
||||
}
|
||||
|
||||
fn runWorker(self: *ThreadPool) void {
|
||||
while (true) {
|
||||
const held = self.lock.acquire();
|
||||
|
||||
if (self.run_queue.popFirst()) |run_node| {
|
||||
held.release();
|
||||
(run_node.data.runFn)(&run_node.data);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!self.is_running) {
|
||||
held.release();
|
||||
return;
|
||||
}
|
||||
|
||||
var idle_node = IdleQueue.Node{ .data = .{} };
|
||||
self.idle_queue.prepend(&idle_node);
|
||||
held.release();
|
||||
idle_node.data.wait();
|
||||
}
|
||||
if (self.idle_queue.popFirst()) |idle_node|
|
||||
idle_node.data.set();
|
||||
}
|
||||
|
||||
@ -5,11 +5,24 @@
|
||||
// and substantial portions of the software.
|
||||
const std = @import("std");
|
||||
const WaitGroup = @This();
|
||||
const Event = @import("Event.zig");
|
||||
|
||||
lock: std.Mutex = .{},
|
||||
counter: usize = 0,
|
||||
event: ?*Event = null,
|
||||
event: std.ResetEvent,
|
||||
|
||||
pub fn init(self: *WaitGroup) !void {
|
||||
self.* = .{
|
||||
.lock = .{},
|
||||
.counter = 0,
|
||||
.event = undefined,
|
||||
};
|
||||
try self.event.init();
|
||||
}
|
||||
|
||||
pub fn deinit(self: *WaitGroup) void {
|
||||
self.event.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn start(self: *WaitGroup) void {
|
||||
const held = self.lock.acquire();
|
||||
@ -18,29 +31,31 @@ pub fn start(self: *WaitGroup) void {
|
||||
self.counter += 1;
|
||||
}
|
||||
|
||||
pub fn stop(self: *WaitGroup) void {
|
||||
var event: ?*Event = null;
|
||||
defer if (event) |waiter|
|
||||
waiter.set();
|
||||
|
||||
pub fn finish(self: *WaitGroup) void {
|
||||
const held = self.lock.acquire();
|
||||
defer held.release();
|
||||
|
||||
self.counter -= 1;
|
||||
if (self.counter == 0)
|
||||
std.mem.swap(?*Event, &self.event, &event);
|
||||
|
||||
if (self.counter == 0) {
|
||||
self.event.set();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait(self: *WaitGroup) void {
|
||||
var event = Event{};
|
||||
var has_event = false;
|
||||
defer if (has_event)
|
||||
event.wait();
|
||||
while (true) {
|
||||
const held = self.lock.acquire();
|
||||
|
||||
const held = self.lock.acquire();
|
||||
defer held.release();
|
||||
if (self.counter == 0) {
|
||||
held.release();
|
||||
return;
|
||||
}
|
||||
|
||||
has_event = self.counter != 0;
|
||||
if (has_event)
|
||||
self.event = &event;
|
||||
held.release();
|
||||
self.event.wait();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(self: *WaitGroup) void {
|
||||
self.event.reset();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user