| author | |
| committer | |
| log | 0fd68f49e2eabb866ea1d21c4657c2a1d3c8ce53 |
| tree | 6217c0d05293c9c1bbb7a8d3c2e24409f0a040c5 |
| parent | 577b57784ae70a85f5f4fef37e749687d0e9b6b0 |
| parent | 87e4f7376aa384183a793cb42498ed0ff06222d5 |
| signature |
More pthreads integration18 files changed, 937 insertions(+), 635 deletions(-)
CMakeLists.txt+3-3| ... | @@ -410,11 +410,12 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -410,11 +410,12 @@ set(ZIG_STAGE2_SOURCES |
| 410 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/bits.zig" | 410 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/bits.zig" |
| 411 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" | 411 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" |
| 412 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" | 412 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" |
| 413 | "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig" | ||
| 414 | "${CMAKE_SOURCE_DIR}/lib/std/ResetEvent.zig" | ||
| 415 | "${CMAKE_SOURCE_DIR}/lib/std/StaticResetEvent.zig" | ||
| 413 | "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig" | 416 | "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig" |
| 414 | "${CMAKE_SOURCE_DIR}/lib/std/process.zig" | 417 | "${CMAKE_SOURCE_DIR}/lib/std/process.zig" |
| 415 | "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig" | ||
| 416 | "${CMAKE_SOURCE_DIR}/lib/std/rand.zig" | 418 | "${CMAKE_SOURCE_DIR}/lib/std/rand.zig" |
| 417 | "${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig" | ||
| 418 | "${CMAKE_SOURCE_DIR}/lib/std/sort.zig" | 419 | "${CMAKE_SOURCE_DIR}/lib/std/sort.zig" |
| 419 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt.zig" | 420 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt.zig" |
| 420 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/addXf3.zig" | 421 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/addXf3.zig" |
| ... | @@ -512,7 +513,6 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -512,7 +513,6 @@ set(ZIG_STAGE2_SOURCES |
| 512 | "${CMAKE_SOURCE_DIR}/src/Cache.zig" | 513 | "${CMAKE_SOURCE_DIR}/src/Cache.zig" |
| 513 | "${CMAKE_SOURCE_DIR}/src/Compilation.zig" | 514 | "${CMAKE_SOURCE_DIR}/src/Compilation.zig" |
| 514 | "${CMAKE_SOURCE_DIR}/src/DepTokenizer.zig" | 515 | "${CMAKE_SOURCE_DIR}/src/DepTokenizer.zig" |
| 515 | "${CMAKE_SOURCE_DIR}/src/Event.zig" | ||
| 516 | "${CMAKE_SOURCE_DIR}/src/Module.zig" | 516 | "${CMAKE_SOURCE_DIR}/src/Module.zig" |
| 517 | "${CMAKE_SOURCE_DIR}/src/Package.zig" | 517 | "${CMAKE_SOURCE_DIR}/src/Package.zig" |
| 518 | "${CMAKE_SOURCE_DIR}/src/RangeSet.zig" | 518 | "${CMAKE_SOURCE_DIR}/src/RangeSet.zig" |
lib/std/Progress.zig+3| ... | @@ -160,6 +160,9 @@ pub fn maybeRefresh(self: *Progress) void { | ... | @@ -160,6 +160,9 @@ pub fn maybeRefresh(self: *Progress) void { |
| 160 | if (now < self.initial_delay_ns) return; | 160 | if (now < self.initial_delay_ns) return; |
| 161 | const held = self.update_lock.tryAcquire() orelse return; | 161 | const held = self.update_lock.tryAcquire() orelse return; |
| 162 | defer held.release(); | 162 | defer held.release(); |
| 163 | // TODO I have observed this to happen sometimes. I think we need to follow Rust's | ||
| 164 | // lead and guarantee monotonically increasing times in the std lib itself. | ||
| 165 | if (now < self.prev_refresh_timestamp) return; | ||
| 163 | if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; | 166 | if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; |
| 164 | return self.refreshWithHeldLock(); | 167 | return self.refreshWithHeldLock(); |
| 165 | } | 168 | } |
lib/std/ResetEvent.zig created+297| ... | @@ -0,0 +1,297 @@ | ||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | // Copyright (c) 2015-2020 Zig Contributors | ||
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| 4 | // The MIT license requires this copyright notice to be included in all copies | ||
| 5 | // and substantial portions of the software. | ||
| 6 | |||
| 7 | //! A thread-safe resource which supports blocking until signaled. | ||
| 8 | //! This API is for kernel threads, not evented I/O. | ||
| 9 | //! This API requires being initialized at runtime, and initialization | ||
| 10 | //! can fail. Once initialized, the core operations cannot fail. | ||
| 11 | //! If you need an abstraction that cannot fail to be initialized, see | ||
| 12 | //! `std.StaticResetEvent`. However if you can handle initialization failure, | ||
| 13 | //! it is preferred to use `ResetEvent`. | ||
| 14 | |||
| 15 | const ResetEvent = @This(); | ||
| 16 | const std = @import("std.zig"); | ||
| 17 | const builtin = std.builtin; | ||
| 18 | const testing = std.testing; | ||
| 19 | const assert = std.debug.assert; | ||
| 20 | const c = std.c; | ||
| 21 | const os = std.os; | ||
| 22 | const time = std.time; | ||
| 23 | |||
| 24 | impl: Impl, | ||
| 25 | |||
| 26 | pub const Impl = if (builtin.single_threaded) | ||
| 27 | std.StaticResetEvent.DebugEvent | ||
| 28 | else if (std.Target.current.isDarwin()) | ||
| 29 | DarwinEvent | ||
| 30 | else if (std.Thread.use_pthreads) | ||
| 31 | PosixEvent | ||
| 32 | else | ||
| 33 | std.StaticResetEvent.AtomicEvent; | ||
| 34 | |||
| 35 | pub const InitError = error{SystemResources}; | ||
| 36 | |||
| 37 | /// After `init`, it is legal to call any other function. | ||
| 38 | pub fn init(ev: *ResetEvent) InitError!void { | ||
| 39 | return ev.impl.init(); | ||
| 40 | } | ||
| 41 | |||
| 42 | /// This function is not thread-safe. | ||
| 43 | /// After `deinit`, the only legal function to call is `init`. | ||
| 44 | pub fn deinit(ev: *ResetEvent) void { | ||
| 45 | return ev.impl.deinit(); | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Sets the event if not already set and wakes up all the threads waiting on | ||
| 49 | /// the event. It is safe to call `set` multiple times before calling `wait`. | ||
| 50 | /// However it is illegal to call `set` after `wait` is called until the event | ||
| 51 | /// is `reset`. This function is thread-safe. | ||
| 52 | pub fn set(ev: *ResetEvent) void { | ||
| 53 | return ev.impl.set(); | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Resets the event to its original, unset state. | ||
| 57 | /// This function is *not* thread-safe. It is equivalent to calling | ||
| 58 | /// `deinit` followed by `init` but without the possibility of failure. | ||
| 59 | pub fn reset(ev: *ResetEvent) void { | ||
| 60 | return ev.impl.reset(); | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Wait for the event to be set by blocking the current thread. | ||
| 64 | /// Thread-safe. No spurious wakeups. | ||
| 65 | /// Upon return from `wait`, the only functions available to be called | ||
| 66 | /// in `ResetEvent` are `reset` and `deinit`. | ||
| 67 | pub fn wait(ev: *ResetEvent) void { | ||
| 68 | return ev.impl.wait(); | ||
| 69 | } | ||
| 70 | |||
| 71 | pub const TimedWaitResult = enum { event_set, timed_out }; | ||
| 72 | |||
| 73 | /// Wait for the event to be set by blocking the current thread. | ||
| 74 | /// A timeout in nanoseconds can be provided as a hint for how | ||
| 75 | /// long the thread should block on the unset event before returning | ||
| 76 | /// `TimedWaitResult.timed_out`. | ||
| 77 | /// Thread-safe. No precision of timing is guaranteed. | ||
| 78 | /// Upon return from `wait`, the only functions available to be called | ||
| 79 | /// in `ResetEvent` are `reset` and `deinit`. | ||
| 80 | pub fn timedWait(ev: *ResetEvent, timeout_ns: u64) TimedWaitResult { | ||
| 81 | return ev.impl.timedWait(timeout_ns); | ||
| 82 | } | ||
| 83 | |||
| 84 | /// Apple has decided to not support POSIX semaphores, so we go with a | ||
| 85 | /// different approach using Grand Central Dispatch. This API is exposed | ||
| 86 | /// by libSystem so it is guaranteed to be available on all Darwin platforms. | ||
| 87 | pub const DarwinEvent = struct { | ||
| 88 | sem: c.dispatch_semaphore_t = undefined, | ||
| 89 | |||
| 90 | pub fn init(ev: *DarwinEvent) !void { | ||
| 91 | ev.* = .{ | ||
| 92 | .sem = c.dispatch_semaphore_create(0) orelse return error.SystemResources, | ||
| 93 | }; | ||
| 94 | } | ||
| 95 | |||
| 96 | pub fn deinit(ev: *DarwinEvent) void { | ||
| 97 | c.dispatch_release(ev.sem); | ||
| 98 | ev.* = undefined; | ||
| 99 | } | ||
| 100 | |||
| 101 | pub fn set(ev: *DarwinEvent) void { | ||
| 102 | // Empirically this returns the numerical value of the semaphore. | ||
| 103 | _ = c.dispatch_semaphore_signal(ev.sem); | ||
| 104 | } | ||
| 105 | |||
| 106 | pub fn wait(ev: *DarwinEvent) void { | ||
| 107 | assert(c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_FOREVER) == 0); | ||
| 108 | } | ||
| 109 | |||
| 110 | pub fn timedWait(ev: *DarwinEvent, timeout_ns: u64) TimedWaitResult { | ||
| 111 | const t = c.dispatch_time(c.DISPATCH_TIME_NOW, @intCast(i64, timeout_ns)); | ||
| 112 | if (c.dispatch_semaphore_wait(ev.sem, t) != 0) { | ||
| 113 | return .timed_out; | ||
| 114 | } else { | ||
| 115 | return .event_set; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | pub fn reset(ev: *DarwinEvent) void { | ||
| 120 | // Keep calling until the semaphore goes back down to 0. | ||
| 121 | while (c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_NOW) == 0) {} | ||
| 122 | } | ||
| 123 | }; | ||
| 124 | |||
| 125 | /// POSIX semaphores must be initialized at runtime because they are allowed to | ||
| 126 | /// be implemented as file descriptors, in which case initialization would require | ||
| 127 | /// a syscall to open the fd. | ||
| 128 | pub const PosixEvent = struct { | ||
| 129 | sem: c.sem_t = undefined, | ||
| 130 | |||
| 131 | pub fn init(ev: *PosixEvent) !void { | ||
| 132 | switch (c.getErrno(c.sem_init(&ev.sem, 0, 0))) { | ||
| 133 | 0 => return, | ||
| 134 | else => return error.SystemResources, | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | pub fn deinit(ev: *PosixEvent) void { | ||
| 139 | assert(c.sem_destroy(&ev.sem) == 0); | ||
| 140 | ev.* = undefined; | ||
| 141 | } | ||
| 142 | |||
| 143 | pub fn set(ev: *PosixEvent) void { | ||
| 144 | assert(c.sem_post(&ev.sem) == 0); | ||
| 145 | } | ||
| 146 | |||
| 147 | pub fn wait(ev: *PosixEvent) void { | ||
| 148 | while (true) { | ||
| 149 | switch (c.getErrno(c.sem_wait(&ev.sem))) { | ||
| 150 | 0 => return, | ||
| 151 | c.EINTR => continue, | ||
| 152 | c.EINVAL => unreachable, | ||
| 153 | else => unreachable, | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | pub fn timedWait(ev: *PosixEvent, timeout_ns: u64) TimedWaitResult { | ||
| 159 | var ts: os.timespec = undefined; | ||
| 160 | var timeout_abs = timeout_ns; | ||
| 161 | os.clock_gettime(os.CLOCK_REALTIME, &ts) catch return .timed_out; | ||
| 162 | timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s; | ||
| 163 | timeout_abs += @intCast(u64, ts.tv_nsec); | ||
| 164 | ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s)); | ||
| 165 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s)); | ||
| 166 | while (true) { | ||
| 167 | switch (c.getErrno(c.sem_timedwait(&ev.sem, &ts))) { | ||
| 168 | 0 => return .event_set, | ||
| 169 | c.EINTR => continue, | ||
| 170 | c.EINVAL => unreachable, | ||
| 171 | c.ETIMEDOUT => return .timed_out, | ||
| 172 | else => unreachable, | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | pub fn reset(ev: *PosixEvent) void { | ||
| 178 | while (true) { | ||
| 179 | switch (c.getErrno(c.sem_trywait(&ev.sem))) { | ||
| 180 | 0 => continue, // Need to make it go to zero. | ||
| 181 | c.EINTR => continue, | ||
| 182 | c.EINVAL => unreachable, | ||
| 183 | c.EAGAIN => return, // The semaphore currently has the value zero. | ||
| 184 | else => unreachable, | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | }; | ||
| 189 | |||
| 190 | test "basic usage" { | ||
| 191 | var event: ResetEvent = undefined; | ||
| 192 | try event.init(); | ||
| 193 | defer event.deinit(); | ||
| 194 | |||
| 195 | // test event setting | ||
| 196 | event.set(); | ||
| 197 | |||
| 198 | // test event resetting | ||
| 199 | event.reset(); | ||
| 200 | |||
| 201 | // test event waiting (non-blocking) | ||
| 202 | event.set(); | ||
| 203 | event.wait(); | ||
| 204 | event.reset(); | ||
| 205 | |||
| 206 | event.set(); | ||
| 207 | testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1)); | ||
| 208 | |||
| 209 | // test cross-thread signaling | ||
| 210 | if (builtin.single_threaded) | ||
| 211 | return; | ||
| 212 | |||
| 213 | const Context = struct { | ||
| 214 | const Self = @This(); | ||
| 215 | |||
| 216 | value: u128, | ||
| 217 | in: ResetEvent, | ||
| 218 | out: ResetEvent, | ||
| 219 | |||
| 220 | fn init(self: *Self) !void { | ||
| 221 | self.* = .{ | ||
| 222 | .value = 0, | ||
| 223 | .in = undefined, | ||
| 224 | .out = undefined, | ||
| 225 | }; | ||
| 226 | try self.in.init(); | ||
| 227 | try self.out.init(); | ||
| 228 | } | ||
| 229 | |||
| 230 | fn deinit(self: *Self) void { | ||
| 231 | self.in.deinit(); | ||
| 232 | self.out.deinit(); | ||
| 233 | self.* = undefined; | ||
| 234 | } | ||
| 235 | |||
| 236 | fn sender(self: *Self) void { | ||
| 237 | // update value and signal input | ||
| 238 | testing.expect(self.value == 0); | ||
| 239 | self.value = 1; | ||
| 240 | self.in.set(); | ||
| 241 | |||
| 242 | // wait for receiver to update value and signal output | ||
| 243 | self.out.wait(); | ||
| 244 | testing.expect(self.value == 2); | ||
| 245 | |||
| 246 | // update value and signal final input | ||
| 247 | self.value = 3; | ||
| 248 | self.in.set(); | ||
| 249 | } | ||
| 250 | |||
| 251 | fn receiver(self: *Self) void { | ||
| 252 | // wait for sender to update value and signal input | ||
| 253 | self.in.wait(); | ||
| 254 | assert(self.value == 1); | ||
| 255 | |||
| 256 | // update value and signal output | ||
| 257 | self.in.reset(); | ||
| 258 | self.value = 2; | ||
| 259 | self.out.set(); | ||
| 260 | |||
| 261 | // wait for sender to update value and signal final input | ||
| 262 | self.in.wait(); | ||
| 263 | assert(self.value == 3); | ||
| 264 | } | ||
| 265 | |||
| 266 | fn sleeper(self: *Self) void { | ||
| 267 | self.in.set(); | ||
| 268 | time.sleep(time.ns_per_ms * 2); | ||
| 269 | self.value = 5; | ||
| 270 | self.out.set(); | ||
| 271 | } | ||
| 272 | |||
| 273 | fn timedWaiter(self: *Self) !void { | ||
| 274 | self.in.wait(); | ||
| 275 | testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us)); | ||
| 276 | try self.out.timedWait(time.ns_per_ms * 100); | ||
| 277 | testing.expect(self.value == 5); | ||
| 278 | } | ||
| 279 | }; | ||
| 280 | |||
| 281 | var context: Context = undefined; | ||
| 282 | try context.init(); | ||
| 283 | defer context.deinit(); | ||
| 284 | const receiver = try std.Thread.spawn(&context, Context.receiver); | ||
| 285 | defer receiver.wait(); | ||
| 286 | context.sender(); | ||
| 287 | |||
| 288 | if (false) { | ||
| 289 | // I have now observed this fail on macOS, Windows, and Linux. | ||
| 290 | // https://github.com/ziglang/zig/issues/7009 | ||
| 291 | var timed = Context.init(); | ||
| 292 | defer timed.deinit(); | ||
| 293 | const sleeper = try std.Thread.spawn(&timed, Context.sleeper); | ||
| 294 | defer sleeper.wait(); | ||
| 295 | try timed.timedWaiter(); | ||
| 296 | } | ||
| 297 | } | ||
lib/std/StaticResetEvent.zig created+396| ... | @@ -0,0 +1,396 @@ | ||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | // Copyright (c) 2015-2020 Zig Contributors | ||
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| 4 | // The MIT license requires this copyright notice to be included in all copies | ||
| 5 | // and substantial portions of the software. | ||
| 6 | |||
| 7 | //! A thread-safe resource which supports blocking until signaled. | ||
| 8 | //! This API is for kernel threads, not evented I/O. | ||
| 9 | //! This API is statically initializable. It cannot fail to be initialized | ||
| 10 | //! and it requires no deinitialization. The downside is that it may not | ||
| 11 | //! integrate as cleanly into other synchronization APIs, or, in a worst case, | ||
| 12 | //! may be forced to fall back on spin locking. As a rule of thumb, prefer | ||
| 13 | //! to use `std.ResetEvent` when possible, and use `StaticResetEvent` when | ||
| 14 | //! the logic needs stronger API guarantees. | ||
| 15 | |||
| 16 | const std = @import("std.zig"); | ||
| 17 | const StaticResetEvent = @This(); | ||
| 18 | const SpinLock = std.SpinLock; | ||
| 19 | const assert = std.debug.assert; | ||
| 20 | const os = std.os; | ||
| 21 | const time = std.time; | ||
| 22 | const linux = std.os.linux; | ||
| 23 | const windows = std.os.windows; | ||
| 24 | const testing = std.testing; | ||
| 25 | |||
| 26 | impl: Impl = .{}, | ||
| 27 | |||
| 28 | pub const Impl = if (std.builtin.single_threaded) | ||
| 29 | DebugEvent | ||
| 30 | else | ||
| 31 | AtomicEvent; | ||
| 32 | |||
| 33 | /// Sets the event if not already set and wakes up all the threads waiting on | ||
| 34 | /// the event. It is safe to call `set` multiple times before calling `wait`. | ||
| 35 | /// However it is illegal to call `set` after `wait` is called until the event | ||
| 36 | /// is `reset`. This function is thread-safe. | ||
| 37 | pub fn set(ev: *StaticResetEvent) void { | ||
| 38 | return ev.impl.set(); | ||
| 39 | } | ||
| 40 | |||
| 41 | /// Wait for the event to be set by blocking the current thread. | ||
| 42 | /// Thread-safe. No spurious wakeups. | ||
| 43 | /// Upon return from `wait`, the only function available to be called | ||
| 44 | /// in `StaticResetEvent` is `reset`. | ||
| 45 | pub fn wait(ev: *StaticResetEvent) void { | ||
| 46 | return ev.impl.wait(); | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Resets the event to its original, unset state. | ||
| 50 | /// This function is *not* thread-safe. It is equivalent to calling | ||
| 51 | /// `deinit` followed by `init` but without the possibility of failure. | ||
| 52 | pub fn reset(ev: *StaticResetEvent) void { | ||
| 53 | return ev.impl.reset(); | ||
| 54 | } | ||
| 55 | |||
| 56 | pub const TimedWaitResult = std.ResetEvent.TimedWaitResult; | ||
| 57 | |||
| 58 | /// Wait for the event to be set by blocking the current thread. | ||
| 59 | /// A timeout in nanoseconds can be provided as a hint for how | ||
| 60 | /// long the thread should block on the unset event before returning | ||
| 61 | /// `TimedWaitResult.timed_out`. | ||
| 62 | /// Thread-safe. No precision of timing is guaranteed. | ||
| 63 | /// Upon return from `timedWait`, the only function available to be called | ||
| 64 | /// in `StaticResetEvent` is `reset`. | ||
| 65 | pub fn timedWait(ev: *StaticResetEvent, timeout_ns: u64) TimedWaitResult { | ||
| 66 | return ev.impl.timedWait(timeout_ns); | ||
| 67 | } | ||
| 68 | |||
| 69 | /// For single-threaded builds, we use this to detect deadlocks. | ||
| 70 | /// In unsafe modes this ends up being no-ops. | ||
| 71 | pub const DebugEvent = struct { | ||
| 72 | state: State = State.unset, | ||
| 73 | |||
| 74 | const State = enum { | ||
| 75 | unset, | ||
| 76 | set, | ||
| 77 | waited, | ||
| 78 | }; | ||
| 79 | |||
| 80 | /// This function is provided so that this type can be re-used inside | ||
| 81 | /// `std.ResetEvent`. | ||
| 82 | pub fn init(ev: *DebugEvent) void { | ||
| 83 | ev.* = .{}; | ||
| 84 | } | ||
| 85 | |||
| 86 | /// This function is provided so that this type can be re-used inside | ||
| 87 | /// `std.ResetEvent`. | ||
| 88 | pub fn deinit(ev: *DebugEvent) void { | ||
| 89 | ev.* = undefined; | ||
| 90 | } | ||
| 91 | |||
| 92 | pub fn set(ev: *DebugEvent) void { | ||
| 93 | switch (ev.state) { | ||
| 94 | .unset => ev.state = .set, | ||
| 95 | .set => {}, | ||
| 96 | .waited => unreachable, // Not allowed to call `set` until `reset`. | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | pub fn wait(ev: *DebugEvent) void { | ||
| 101 | switch (ev.state) { | ||
| 102 | .unset => unreachable, // Deadlock detected. | ||
| 103 | .set => return, | ||
| 104 | .waited => unreachable, // Not allowed to call `wait` until `reset`. | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult { | ||
| 109 | switch (ev.state) { | ||
| 110 | .unset => return .timed_out, | ||
| 111 | .set => return .event_set, | ||
| 112 | .waited => unreachable, // Not allowed to call `wait` until `reset`. | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | pub fn reset(ev: *DebugEvent) void { | ||
| 117 | ev.state = .unset; | ||
| 118 | } | ||
| 119 | }; | ||
| 120 | |||
| 121 | pub const AtomicEvent = struct { | ||
| 122 | waiters: u32 = 0, | ||
| 123 | |||
| 124 | const WAKE = 1 << 0; | ||
| 125 | const WAIT = 1 << 1; | ||
| 126 | |||
| 127 | /// This function is provided so that this type can be re-used inside | ||
| 128 | /// `std.ResetEvent`. | ||
| 129 | pub fn init(ev: *AtomicEvent) void { | ||
| 130 | ev.* = .{}; | ||
| 131 | } | ||
| 132 | |||
| 133 | /// This function is provided so that this type can be re-used inside | ||
| 134 | /// `std.ResetEvent`. | ||
| 135 | pub fn deinit(ev: *AtomicEvent) void { | ||
| 136 | ev.* = undefined; | ||
| 137 | } | ||
| 138 | |||
| 139 | pub fn set(ev: *AtomicEvent) void { | ||
| 140 | const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release); | ||
| 141 | if (waiters >= WAIT) { | ||
| 142 | return Futex.wake(&ev.waiters, waiters >> 1); | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | pub fn wait(ev: *AtomicEvent) void { | ||
| 147 | switch (ev.timedWait(null)) { | ||
| 148 | .timed_out => unreachable, | ||
| 149 | .event_set => return, | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | pub fn timedWait(ev: *AtomicEvent, timeout: ?u64) TimedWaitResult { | ||
| 154 | var waiters = @atomicLoad(u32, &ev.waiters, .Acquire); | ||
| 155 | while (waiters != WAKE) { | ||
| 156 | waiters = @cmpxchgWeak(u32, &ev.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse { | ||
| 157 | if (Futex.wait(&ev.waiters, timeout)) |_| { | ||
| 158 | return .event_set; | ||
| 159 | } else |_| { | ||
| 160 | return .timed_out; | ||
| 161 | } | ||
| 162 | }; | ||
| 163 | } | ||
| 164 | return .event_set; | ||
| 165 | } | ||
| 166 | |||
| 167 | pub fn reset(ev: *AtomicEvent) void { | ||
| 168 | @atomicStore(u32, &ev.waiters, 0, .Monotonic); | ||
| 169 | } | ||
| 170 | |||
| 171 | pub const Futex = switch (std.Target.current.os.tag) { | ||
| 172 | .windows => WindowsFutex, | ||
| 173 | .linux => LinuxFutex, | ||
| 174 | else => SpinFutex, | ||
| 175 | }; | ||
| 176 | |||
| 177 | pub const SpinFutex = struct { | ||
| 178 | fn wake(waiters: *u32, wake_count: u32) void {} | ||
| 179 | |||
| 180 | fn wait(waiters: *u32, timeout: ?u64) !void { | ||
| 181 | var timer: time.Timer = undefined; | ||
| 182 | if (timeout != null) | ||
| 183 | timer = time.Timer.start() catch return error.TimedOut; | ||
| 184 | |||
| 185 | while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { | ||
| 186 | SpinLock.yield(); | ||
| 187 | if (timeout) |timeout_ns| { | ||
| 188 | if (timer.read() >= timeout_ns) | ||
| 189 | return error.TimedOut; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | }; | ||
| 194 | |||
| 195 | pub const LinuxFutex = struct { | ||
| 196 | fn wake(waiters: *u32, wake_count: u32) void { | ||
| 197 | const waiting = std.math.maxInt(i32); // wake_count | ||
| 198 | const ptr = @ptrCast(*const i32, waiters); | ||
| 199 | const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting); | ||
| 200 | assert(linux.getErrno(rc) == 0); | ||
| 201 | } | ||
| 202 | |||
| 203 | fn wait(waiters: *u32, timeout: ?u64) !void { | ||
| 204 | var ts: linux.timespec = undefined; | ||
| 205 | var ts_ptr: ?*linux.timespec = null; | ||
| 206 | if (timeout) |timeout_ns| { | ||
| 207 | ts_ptr = &ts; | ||
| 208 | ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s); | ||
| 209 | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); | ||
| 210 | } | ||
| 211 | |||
| 212 | while (true) { | ||
| 213 | const waiting = @atomicLoad(u32, waiters, .Acquire); | ||
| 214 | if (waiting == WAKE) | ||
| 215 | return; | ||
| 216 | const expected = @intCast(i32, waiting); | ||
| 217 | const ptr = @ptrCast(*const i32, waiters); | ||
| 218 | const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr); | ||
| 219 | switch (linux.getErrno(rc)) { | ||
| 220 | 0 => continue, | ||
| 221 | os.ETIMEDOUT => return error.TimedOut, | ||
| 222 | os.EINTR => continue, | ||
| 223 | os.EAGAIN => return, | ||
| 224 | else => unreachable, | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | }; | ||
| 229 | |||
| 230 | pub const WindowsFutex = struct { | ||
| 231 | pub fn wake(waiters: *u32, wake_count: u32) void { | ||
| 232 | const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count); | ||
| 233 | const key = @ptrCast(*const c_void, waiters); | ||
| 234 | |||
| 235 | var waiting = wake_count; | ||
| 236 | while (waiting != 0) : (waiting -= 1) { | ||
| 237 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); | ||
| 238 | assert(rc == .SUCCESS); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | pub fn wait(waiters: *u32, timeout: ?u64) !void { | ||
| 243 | const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout); | ||
| 244 | const key = @ptrCast(*const c_void, waiters); | ||
| 245 | |||
| 246 | // NT uses timeouts in units of 100ns with negative value being relative | ||
| 247 | var timeout_ptr: ?*windows.LARGE_INTEGER = null; | ||
| 248 | var timeout_value: windows.LARGE_INTEGER = undefined; | ||
| 249 | if (timeout) |timeout_ns| { | ||
| 250 | timeout_ptr = &timeout_value; | ||
| 251 | timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100); | ||
| 252 | } | ||
| 253 | |||
| 254 | // NtWaitForKeyedEvent doesnt have spurious wake-ups | ||
| 255 | var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); | ||
| 256 | switch (rc) { | ||
| 257 | .TIMEOUT => { | ||
| 258 | // update the wait count to signal that we're not waiting anymore. | ||
| 259 | // if the .set() thread already observed that we are, perform a | ||
| 260 | // matching NtWaitForKeyedEvent so that the .set() thread doesn't | ||
| 261 | // deadlock trying to run NtReleaseKeyedEvent above. | ||
| 262 | var waiting = @atomicLoad(u32, waiters, .Monotonic); | ||
| 263 | while (true) { | ||
| 264 | if (waiting == WAKE) { | ||
| 265 | rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); | ||
| 266 | assert(rc == .WAIT_0); | ||
| 267 | break; | ||
| 268 | } else { | ||
| 269 | waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break; | ||
| 270 | continue; | ||
| 271 | } | ||
| 272 | } | ||
| 273 | return error.TimedOut; | ||
| 274 | }, | ||
| 275 | .WAIT_0 => {}, | ||
| 276 | else => unreachable, | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | var event_handle: usize = EMPTY; | ||
| 281 | const EMPTY = ~@as(usize, 0); | ||
| 282 | const LOADING = EMPTY - 1; | ||
| 283 | |||
| 284 | pub fn getEventHandle() ?windows.HANDLE { | ||
| 285 | var handle = @atomicLoad(usize, &event_handle, .Monotonic); | ||
| 286 | while (true) { | ||
| 287 | switch (handle) { | ||
| 288 | EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse { | ||
| 289 | const handle_ptr = @ptrCast(*windows.HANDLE, &handle); | ||
| 290 | const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; | ||
| 291 | if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS) | ||
| 292 | handle = 0; | ||
| 293 | @atomicStore(usize, &event_handle, handle, .Monotonic); | ||
| 294 | return @intToPtr(?windows.HANDLE, handle); | ||
| 295 | }, | ||
| 296 | LOADING => { | ||
| 297 | SpinLock.yield(); | ||
| 298 | handle = @atomicLoad(usize, &event_handle, .Monotonic); | ||
| 299 | }, | ||
| 300 | else => { | ||
| 301 | return @intToPtr(?windows.HANDLE, handle); | ||
| 302 | }, | ||
| 303 | } | ||
| 304 | } | ||
| 305 | } | ||
| 306 | }; | ||
| 307 | }; | ||
| 308 | |||
| 309 | test "basic usage" { | ||
| 310 | var event = StaticResetEvent{}; | ||
| 311 | |||
| 312 | // test event setting | ||
| 313 | event.set(); | ||
| 314 | |||
| 315 | // test event resetting | ||
| 316 | event.reset(); | ||
| 317 | |||
| 318 | // test event waiting (non-blocking) | ||
| 319 | event.set(); | ||
| 320 | event.wait(); | ||
| 321 | event.reset(); | ||
| 322 | |||
| 323 | event.set(); | ||
| 324 | testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1)); | ||
| 325 | |||
| 326 | // test cross-thread signaling | ||
| 327 | if (std.builtin.single_threaded) | ||
| 328 | return; | ||
| 329 | |||
| 330 | const Context = struct { | ||
| 331 | const Self = @This(); | ||
| 332 | |||
| 333 | value: u128 = 0, | ||
| 334 | in: StaticResetEvent = .{}, | ||
| 335 | out: StaticResetEvent = .{}, | ||
| 336 | |||
| 337 | fn sender(self: *Self) void { | ||
| 338 | // update value and signal input | ||
| 339 | testing.expect(self.value == 0); | ||
| 340 | self.value = 1; | ||
| 341 | self.in.set(); | ||
| 342 | |||
| 343 | // wait for receiver to update value and signal output | ||
| 344 | self.out.wait(); | ||
| 345 | testing.expect(self.value == 2); | ||
| 346 | |||
| 347 | // update value and signal final input | ||
| 348 | self.value = 3; | ||
| 349 | self.in.set(); | ||
| 350 | } | ||
| 351 | |||
| 352 | fn receiver(self: *Self) void { | ||
| 353 | // wait for sender to update value and signal input | ||
| 354 | self.in.wait(); | ||
| 355 | assert(self.value == 1); | ||
| 356 | |||
| 357 | // update value and signal output | ||
| 358 | self.in.reset(); | ||
| 359 | self.value = 2; | ||
| 360 | self.out.set(); | ||
| 361 | |||
| 362 | // wait for sender to update value and signal final input | ||
| 363 | self.in.wait(); | ||
| 364 | assert(self.value == 3); | ||
| 365 | } | ||
| 366 | |||
| 367 | fn sleeper(self: *Self) void { | ||
| 368 | self.in.set(); | ||
| 369 | time.sleep(time.ns_per_ms * 2); | ||
| 370 | self.value = 5; | ||
| 371 | self.out.set(); | ||
| 372 | } | ||
| 373 | |||
| 374 | fn timedWaiter(self: *Self) !void { | ||
| 375 | self.in.wait(); | ||
| 376 | testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us)); | ||
| 377 | try self.out.timedWait(time.ns_per_ms * 100); | ||
| 378 | testing.expect(self.value == 5); | ||
| 379 | } | ||
| 380 | }; | ||
| 381 | |||
| 382 | var context = Context{}; | ||
| 383 | const receiver = try std.Thread.spawn(&context, Context.receiver); | ||
| 384 | defer receiver.wait(); | ||
| 385 | context.sender(); | ||
| 386 | |||
| 387 | if (false) { | ||
| 388 | // I have now observed this fail on macOS, Windows, and Linux. | ||
| 389 | // https://github.com/ziglang/zig/issues/7009 | ||
| 390 | var timed = Context.init(); | ||
| 391 | defer timed.deinit(); | ||
| 392 | const sleeper = try std.Thread.spawn(&timed, Context.sleeper); | ||
| 393 | defer sleeper.wait(); | ||
| 394 | try timed.timedWaiter(); | ||
| 395 | } | ||
| 396 | } | ||
lib/std/auto_reset_event.zig+21-22| ... | @@ -7,14 +7,15 @@ const std = @import("std.zig"); | ... | @@ -7,14 +7,15 @@ const std = @import("std.zig"); |
| 7 | const builtin = @import("builtin"); | 7 | const builtin = @import("builtin"); |
| 8 | const testing = std.testing; | 8 | const testing = std.testing; |
| 9 | const assert = std.debug.assert; | 9 | const assert = std.debug.assert; |
| 10 | const StaticResetEvent = std.StaticResetEvent; | ||
| 10 | 11 | ||
| 11 | /// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`. | 12 | /// Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`. |
| 12 | /// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like). | 13 | /// Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like). |
| 13 | pub const AutoResetEvent = struct { | 14 | pub const AutoResetEvent = struct { |
| 14 | /// AutoResetEvent has 3 possible states: | 15 | /// AutoResetEvent has 3 possible states: |
| 15 | /// - UNSET: the AutoResetEvent is currently unset | 16 | /// - UNSET: the AutoResetEvent is currently unset |
| 16 | /// - SET: the AutoResetEvent was notified before a wait() was called | 17 | /// - SET: the AutoResetEvent was notified before a wait() was called |
| 17 | /// - <std.ResetEvent pointer>: there is an active waiter waiting for a notification. | 18 | /// - <StaticResetEvent pointer>: there is an active waiter waiting for a notification. |
| 18 | /// | 19 | /// |
| 19 | /// When attempting to wait: | 20 | /// When attempting to wait: |
| 20 | /// if the event is unset, it registers a ResetEvent pointer to be notified when the event is set | 21 | /// 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 { | ... | @@ -25,20 +26,20 @@ pub const AutoResetEvent = struct { |
| 25 | /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent | 26 | /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent |
| 26 | /// | 27 | /// |
| 27 | /// This ensures that the event is automatically reset after a wait() has been issued | 28 | /// This ensures that the event is automatically reset after a wait() has been issued |
| 28 | /// and avoids the race condition when using std.ResetEvent in the following scenario: | 29 | /// and avoids the race condition when using StaticResetEvent in the following scenario: |
| 29 | /// thread 1 | thread 2 | 30 | /// thread 1 | thread 2 |
| 30 | /// std.ResetEvent.wait() | | 31 | /// StaticResetEvent.wait() | |
| 31 | /// | std.ResetEvent.set() | 32 | /// | StaticResetEvent.set() |
| 32 | /// | std.ResetEvent.set() | 33 | /// | StaticResetEvent.set() |
| 33 | /// std.ResetEvent.reset() | | 34 | /// StaticResetEvent.reset() | |
| 34 | /// std.ResetEvent.wait() | (missed the second .set() notification above) | 35 | /// StaticResetEvent.wait() | (missed the second .set() notification above) |
| 35 | state: usize = UNSET, | 36 | state: usize = UNSET, |
| 36 | 37 | ||
| 37 | const UNSET = 0; | 38 | const UNSET = 0; |
| 38 | const SET = 1; | 39 | const SET = 1; |
| 39 | 40 | ||
| 40 | /// the minimum alignment for the `*std.ResetEvent` created by wait*() | 41 | /// the minimum alignment for the `*StaticResetEvent` created by wait*() |
| 41 | const event_align = std.math.max(@alignOf(std.ResetEvent), 2); | 42 | const event_align = std.math.max(@alignOf(StaticResetEvent), 2); |
| 42 | 43 | ||
| 43 | pub fn wait(self: *AutoResetEvent) void { | 44 | pub fn wait(self: *AutoResetEvent) void { |
| 44 | self.waitFor(null) catch unreachable; | 45 | self.waitFor(null) catch unreachable; |
| ... | @@ -49,12 +50,9 @@ pub const AutoResetEvent = struct { | ... | @@ -49,12 +50,9 @@ pub const AutoResetEvent = struct { |
| 49 | } | 50 | } |
| 50 | 51 | ||
| 51 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { | 52 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { |
| 52 | // lazily initialized std.ResetEvent | 53 | // lazily initialized StaticResetEvent |
| 53 | var reset_event: std.ResetEvent align(event_align) = undefined; | 54 | var reset_event: StaticResetEvent align(event_align) = undefined; |
| 54 | var has_reset_event = false; | 55 | var has_reset_event = false; |
| 55 | defer if (has_reset_event) { | ||
| 56 | reset_event.deinit(); | ||
| 57 | }; | ||
| 58 | 56 | ||
| 59 | var state = @atomicLoad(usize, &self.state, .SeqCst); | 57 | var state = @atomicLoad(usize, &self.state, .SeqCst); |
| 60 | while (true) { | 58 | while (true) { |
| ... | @@ -72,7 +70,7 @@ pub const AutoResetEvent = struct { | ... | @@ -72,7 +70,7 @@ pub const AutoResetEvent = struct { |
| 72 | // lazily initialize the ResetEvent if it hasn't been already | 70 | // lazily initialize the ResetEvent if it hasn't been already |
| 73 | if (!has_reset_event) { | 71 | if (!has_reset_event) { |
| 74 | has_reset_event = true; | 72 | has_reset_event = true; |
| 75 | reset_event = std.ResetEvent.init(); | 73 | reset_event = .{}; |
| 76 | } | 74 | } |
| 77 | 75 | ||
| 78 | // Since the AutoResetEvent currently isnt set, | 76 | // Since the AutoResetEvent currently isnt set, |
| ... | @@ -97,9 +95,10 @@ pub const AutoResetEvent = struct { | ... | @@ -97,9 +95,10 @@ pub const AutoResetEvent = struct { |
| 97 | }; | 95 | }; |
| 98 | 96 | ||
| 99 | // wait with a timeout and return if signalled via set() | 97 | // wait with a timeout and return if signalled via set() |
| 100 | if (reset_event.timedWait(timeout_ns)) |_| { | 98 | switch (reset_event.timedWait(timeout_ns)) { |
| 101 | return; | 99 | .event_set => return, |
| 102 | } else |timed_out| {} | 100 | .timed_out => {}, |
| 101 | } | ||
| 103 | 102 | ||
| 104 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. | 103 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. |
| 105 | // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. | 104 | // 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 { | ... | @@ -164,7 +163,7 @@ pub const AutoResetEvent = struct { |
| 164 | continue; | 163 | continue; |
| 165 | } | 164 | } |
| 166 | 165 | ||
| 167 | const reset_event = @intToPtr(*align(event_align) std.ResetEvent, state); | 166 | const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state); |
| 168 | reset_event.set(); | 167 | reset_event.set(); |
| 169 | return; | 168 | return; |
| 170 | } | 169 | } |
lib/std/c.zig+8| ... | @@ -270,6 +270,13 @@ pub extern "c" fn pthread_atfork( | ... | @@ -270,6 +270,13 @@ pub extern "c" fn pthread_atfork( |
| 270 | parent: ?fn () callconv(.C) void, | 270 | parent: ?fn () callconv(.C) void, |
| 271 | child: ?fn () callconv(.C) void, | 271 | child: ?fn () callconv(.C) void, |
| 272 | ) c_int; | 272 | ) c_int; |
| 273 | pub extern "c" fn sem_init(sem: *sem_t, pshared: c_int, value: c_uint) c_int; | ||
| 274 | pub extern "c" fn sem_destroy(sem: *sem_t) c_int; | ||
| 275 | pub extern "c" fn sem_post(sem: *sem_t) c_int; | ||
| 276 | pub extern "c" fn sem_wait(sem: *sem_t) c_int; | ||
| 277 | pub extern "c" fn sem_trywait(sem: *sem_t) c_int; | ||
| 278 | pub extern "c" fn sem_timedwait(sem: *sem_t, abs_timeout: *const timespec) c_int; | ||
| 279 | pub extern "c" fn sem_getvalue(sem: *sem_t, sval: *c_int) c_int; | ||
| 273 | 280 | ||
| 274 | pub extern "c" fn kqueue() c_int; | 281 | pub extern "c" fn kqueue() c_int; |
| 275 | pub extern "c" fn kevent( | 282 | pub extern "c" fn kevent( |
| ... | @@ -316,6 +323,7 @@ pub extern "c" fn dn_expand( | ... | @@ -316,6 +323,7 @@ pub extern "c" fn dn_expand( |
| 316 | pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{}; | 323 | pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{}; |
| 317 | pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) c_int; | 324 | pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) c_int; |
| 318 | pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) c_int; | 325 | pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) c_int; |
| 326 | pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) c_int; | ||
| 319 | pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int; | 327 | pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int; |
| 320 | 328 | ||
| 321 | pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{}; | 329 | pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{}; |
lib/std/c/darwin.zig+13| ... | @@ -177,6 +177,7 @@ pub const pthread_cond_t = extern struct { | ... | @@ -177,6 +177,7 @@ pub const pthread_cond_t = extern struct { |
| 177 | __sig: c_long = 0x3CB0B1BB, | 177 | __sig: c_long = 0x3CB0B1BB, |
| 178 | __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__, | 178 | __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__, |
| 179 | }; | 179 | }; |
| 180 | pub const sem_t = c_int; | ||
| 180 | const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40; | 181 | const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40; |
| 181 | const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24; | 182 | const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24; |
| 182 | 183 | ||
| ... | @@ -186,3 +187,15 @@ pub const pthread_attr_t = extern struct { | ... | @@ -186,3 +187,15 @@ pub const pthread_attr_t = extern struct { |
| 186 | }; | 187 | }; |
| 187 | 188 | ||
| 188 | pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; | 189 | pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; |
| 190 | |||
| 191 | // Grand Central Dispatch is exposed by libSystem. | ||
| 192 | pub const dispatch_semaphore_t = *opaque{}; | ||
| 193 | pub const dispatch_time_t = u64; | ||
| 194 | pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0); | ||
| 195 | pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0); | ||
| 196 | pub extern "c" fn dispatch_semaphore_create(value: isize) ?dispatch_semaphore_t; | ||
| 197 | pub extern "c" fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) isize; | ||
| 198 | pub extern "c" fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) isize; | ||
| 199 | |||
| 200 | pub extern "c" fn dispatch_release(object: *c_void) void; | ||
| 201 | pub extern "c" fn dispatch_time(when: dispatch_time_t, delta: i64) dispatch_time_t; |
lib/std/c/freebsd.zig+9| ... | @@ -47,6 +47,15 @@ pub const pthread_attr_t = extern struct { | ... | @@ -47,6 +47,15 @@ pub const pthread_attr_t = extern struct { |
| 47 | __align: c_long, | 47 | __align: c_long, |
| 48 | }; | 48 | }; |
| 49 | 49 | ||
| 50 | pub const sem_t = extern struct { | ||
| 51 | _magic: u32, | ||
| 52 | _kern: extern struct { | ||
| 53 | _count: u32, | ||
| 54 | _flags: u32, | ||
| 55 | }, | ||
| 56 | _padding: u32, | ||
| 57 | }; | ||
| 58 | |||
| 50 | pub const EAI = extern enum(c_int) { | 59 | pub const EAI = extern enum(c_int) { |
| 51 | /// address family for hostname not supported | 60 | /// address family for hostname not supported |
| 52 | ADDRFAMILY = 1, | 61 | ADDRFAMILY = 1, |
lib/std/c/linux.zig+5| ... | @@ -123,6 +123,10 @@ pub const pthread_mutex_t = extern struct { | ... | @@ -123,6 +123,10 @@ pub const pthread_mutex_t = extern struct { |
| 123 | pub const pthread_cond_t = extern struct { | 123 | pub const pthread_cond_t = extern struct { |
| 124 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, | 124 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, |
| 125 | }; | 125 | }; |
| 126 | pub const sem_t = extern struct { | ||
| 127 | __size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)), | ||
| 128 | }; | ||
| 129 | |||
| 126 | const __SIZEOF_PTHREAD_COND_T = 48; | 130 | const __SIZEOF_PTHREAD_COND_T = 48; |
| 127 | const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) { | 131 | const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) { |
| 128 | .musl, .musleabi, .musleabihf => if (@sizeOf(usize) == 8) 40 else 24, | 132 | .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 | ... | @@ -134,6 +138,7 @@ const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch |
| 134 | }, | 138 | }, |
| 135 | else => unreachable, | 139 | else => unreachable, |
| 136 | }; | 140 | }; |
| 141 | const __SIZEOF_SEM_T = 4 * @sizeOf(usize); | ||
| 137 | 142 | ||
| 138 | pub const RTLD_LAZY = 1; | 143 | pub const RTLD_LAZY = 1; |
| 139 | pub const RTLD_NOW = 2; | 144 | pub const RTLD_NOW = 2; |
lib/std/debug.zig+1-2| ... | @@ -274,9 +274,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c | ... | @@ -274,9 +274,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 274 | // and call abort() | 274 | // and call abort() |
| 275 | 275 | ||
| 276 | // Sleep forever without hammering the CPU | 276 | // Sleep forever without hammering the CPU |
| 277 | var event = std.ResetEvent.init(); | 277 | var event: std.StaticResetEvent = .{}; |
| 278 | event.wait(); | 278 | event.wait(); |
| 279 | |||
| 280 | unreachable; | 279 | unreachable; |
| 281 | } | 280 | } |
| 282 | }, | 281 | }, |
lib/std/fs/test.zig+2-3| ... | @@ -758,7 +758,8 @@ test "open file with exclusive lock twice, make sure it waits" { | ... | @@ -758,7 +758,8 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 758 | } | 758 | } |
| 759 | }; | 759 | }; |
| 760 | 760 | ||
| 761 | var evt = std.ResetEvent.init(); | 761 | var evt: std.ResetEvent = undefined; |
| 762 | try evt.init(); | ||
| 762 | defer evt.deinit(); | 763 | defer evt.deinit(); |
| 763 | 764 | ||
| 764 | const t = try std.Thread.spawn(S.C{ .dir = &tmp.dir, .evt = &evt }, S.checkFn); | 765 | 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" { | ... | @@ -771,8 +772,6 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 771 | std.time.sleep(SLEEP_TIMEOUT_NS); | 772 | std.time.sleep(SLEEP_TIMEOUT_NS); |
| 772 | if (timer.read() >= SLEEP_TIMEOUT_NS) break; | 773 | if (timer.read() >= SLEEP_TIMEOUT_NS) break; |
| 773 | } | 774 | } |
| 774 | // Check that createFile is still waiting for the lock to be released. | ||
| 775 | testing.expect(!evt.isSet()); | ||
| 776 | file.close(); | 775 | file.close(); |
| 777 | // No timeout to avoid failures on heavily loaded systems. | 776 | // No timeout to avoid failures on heavily loaded systems. |
| 778 | evt.wait(); | 777 | evt.wait(); |
lib/std/mutex.zig+57-8| ... | @@ -10,7 +10,7 @@ const assert = std.debug.assert; | ... | @@ -10,7 +10,7 @@ const assert = std.debug.assert; |
| 10 | const windows = os.windows; | 10 | const windows = os.windows; |
| 11 | const testing = std.testing; | 11 | const testing = std.testing; |
| 12 | const SpinLock = std.SpinLock; | 12 | const SpinLock = std.SpinLock; |
| 13 | const ResetEvent = std.ResetEvent; | 13 | const StaticResetEvent = std.StaticResetEvent; |
| 14 | 14 | ||
| 15 | /// Lock may be held only once. If the same thread tries to acquire | 15 | /// Lock may be held only once. If the same thread tries to acquire |
| 16 | /// the same mutex twice, it deadlocks. This type supports static | 16 | /// the same mutex twice, it deadlocks. This type supports static |
| ... | @@ -37,6 +37,8 @@ pub const Mutex = if (builtin.single_threaded) | ... | @@ -37,6 +37,8 @@ pub const Mutex = if (builtin.single_threaded) |
| 37 | Dummy | 37 | Dummy |
| 38 | else if (builtin.os.tag == .windows) | 38 | else if (builtin.os.tag == .windows) |
| 39 | WindowsMutex | 39 | WindowsMutex |
| 40 | else if (std.Thread.use_pthreads) | ||
| 41 | PthreadMutex | ||
| 40 | else if (builtin.link_libc or builtin.os.tag == .linux) | 42 | else if (builtin.link_libc or builtin.os.tag == .linux) |
| 41 | // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs | 43 | // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs |
| 42 | struct { | 44 | struct { |
| ... | @@ -52,7 +54,7 @@ else if (builtin.link_libc or builtin.os.tag == .linux) | ... | @@ -52,7 +54,7 @@ else if (builtin.link_libc or builtin.os.tag == .linux) |
| 52 | 54 | ||
| 53 | const Node = struct { | 55 | const Node = struct { |
| 54 | next: ?*Node, | 56 | next: ?*Node, |
| 55 | event: ResetEvent, | 57 | event: StaticResetEvent, |
| 56 | }; | 58 | }; |
| 57 | 59 | ||
| 58 | pub fn tryAcquire(self: *Mutex) ?Held { | 60 | pub fn tryAcquire(self: *Mutex) ?Held { |
| ... | @@ -88,11 +90,12 @@ else if (builtin.link_libc or builtin.os.tag == .linux) | ... | @@ -88,11 +90,12 @@ else if (builtin.link_libc or builtin.os.tag == .linux) |
| 88 | state = @atomicLoad(usize, &self.state, .Monotonic); | 90 | state = @atomicLoad(usize, &self.state, .Monotonic); |
| 89 | } | 91 | } |
| 90 | 92 | ||
| 91 | // create the ResetEvent node on the stack | 93 | // create the StaticResetEvent node on the stack |
| 92 | // (faster than threadlocal on platforms like OSX) | 94 | // (faster than threadlocal on platforms like OSX) |
| 93 | var node: Node = undefined; | 95 | var node: Node = .{ |
| 94 | node.event = ResetEvent.init(); | 96 | .next = undefined, |
| 95 | defer node.event.deinit(); | 97 | .event = .{}, |
| 98 | }; | ||
| 96 | 99 | ||
| 97 | // we've spun too long, try and add our node to the LIFO queue. | 100 | // we've spun too long, try and add our node to the LIFO queue. |
| 98 | // if the mutex becomes available in the process, try and grab it instead. | 101 | // 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) | ... | @@ -166,6 +169,52 @@ else if (builtin.link_libc or builtin.os.tag == .linux) |
| 166 | else | 169 | else |
| 167 | SpinLock; | 170 | SpinLock; |
| 168 | 171 | ||
| 172 | pub const PthreadMutex = struct { | ||
| 173 | pthread_mutex: std.c.pthread_mutex_t = init, | ||
| 174 | |||
| 175 | pub const Held = struct { | ||
| 176 | mutex: *PthreadMutex, | ||
| 177 | |||
| 178 | pub fn release(self: Held) void { | ||
| 179 | switch (std.c.pthread_mutex_unlock(&self.mutex.pthread_mutex)) { | ||
| 180 | 0 => return, | ||
| 181 | std.c.EINVAL => unreachable, | ||
| 182 | std.c.EAGAIN => unreachable, | ||
| 183 | std.c.EPERM => unreachable, | ||
| 184 | else => unreachable, | ||
| 185 | } | ||
| 186 | } | ||
| 187 | }; | ||
| 188 | |||
| 189 | /// Create a new mutex in unlocked state. | ||
| 190 | pub const init = std.c.PTHREAD_MUTEX_INITIALIZER; | ||
| 191 | |||
| 192 | /// Try to acquire the mutex without blocking. Returns null if | ||
| 193 | /// the mutex is unavailable. Otherwise returns Held. Call | ||
| 194 | /// release on Held. | ||
| 195 | pub fn tryAcquire(self: *PthreadMutex) ?Held { | ||
| 196 | if (std.c.pthread_mutex_trylock(&self.pthread_mutex) == 0) { | ||
| 197 | return Held{ .mutex = self }; | ||
| 198 | } else { | ||
| 199 | return null; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | /// Acquire the mutex. Will deadlock if the mutex is already | ||
| 204 | /// held by the calling thread. | ||
| 205 | pub fn acquire(self: *PthreadMutex) Held { | ||
| 206 | switch (std.c.pthread_mutex_lock(&self.pthread_mutex)) { | ||
| 207 | 0 => return Held{ .mutex = self }, | ||
| 208 | std.c.EINVAL => unreachable, | ||
| 209 | std.c.EBUSY => unreachable, | ||
| 210 | std.c.EAGAIN => unreachable, | ||
| 211 | std.c.EDEADLK => unreachable, | ||
| 212 | std.c.EPERM => unreachable, | ||
| 213 | else => unreachable, | ||
| 214 | } | ||
| 215 | } | ||
| 216 | }; | ||
| 217 | |||
| 169 | /// This has the sematics as `Mutex`, however it does not actually do any | 218 | /// This has the sematics as `Mutex`, however it does not actually do any |
| 170 | /// synchronization. Operations are safety-checked no-ops. | 219 | /// synchronization. Operations are safety-checked no-ops. |
| 171 | pub const Dummy = struct { | 220 | pub const Dummy = struct { |
| ... | @@ -236,7 +285,7 @@ const WindowsMutex = struct { | ... | @@ -236,7 +285,7 @@ const WindowsMutex = struct { |
| 236 | fn acquireSlow(self: *WindowsMutex) Held { | 285 | fn acquireSlow(self: *WindowsMutex) Held { |
| 237 | // try to use NT keyed events for blocking, falling back to spinlock if unavailable | 286 | // try to use NT keyed events for blocking, falling back to spinlock if unavailable |
| 238 | @setCold(true); | 287 | @setCold(true); |
| 239 | const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning(); | 288 | const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return self.acquireSpinning(); |
| 240 | const key = @ptrCast(*const c_void, &self.state.waiters); | 289 | const key = @ptrCast(*const c_void, &self.state.waiters); |
| 241 | 290 | ||
| 242 | while (true) : (SpinLock.loopHint(1)) { | 291 | while (true) : (SpinLock.loopHint(1)) { |
| ... | @@ -264,7 +313,7 @@ const WindowsMutex = struct { | ... | @@ -264,7 +313,7 @@ const WindowsMutex = struct { |
| 264 | pub fn release(self: Held) void { | 313 | pub fn release(self: Held) void { |
| 265 | // unlock without a rmw/cmpxchg instruction | 314 | // unlock without a rmw/cmpxchg instruction |
| 266 | @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release); | 315 | @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release); |
| 267 | const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return; | 316 | const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return; |
| 268 | const key = @ptrCast(*const c_void, &self.mutex.state.waiters); | 317 | const key = @ptrCast(*const c_void, &self.mutex.state.waiters); |
| 269 | 318 | ||
| 270 | while (true) : (SpinLock.loopHint(1)) { | 319 | while (true) : (SpinLock.loopHint(1)) { |
lib/std/reset_event.zig deleted-468| ... | @@ -1,468 +0,0 @@ | ||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | // Copyright (c) 2015-2020 Zig Contributors | ||
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| 4 | // The MIT license requires this copyright notice to be included in all copies | ||
| 5 | // and substantial portions of the software. | ||
| 6 | const std = @import("std.zig"); | ||
| 7 | const builtin = @import("builtin"); | ||
| 8 | const testing = std.testing; | ||
| 9 | const SpinLock = std.SpinLock; | ||
| 10 | const assert = std.debug.assert; | ||
| 11 | const c = std.c; | ||
| 12 | const os = std.os; | ||
| 13 | const time = std.time; | ||
| 14 | const linux = os.linux; | ||
| 15 | const windows = os.windows; | ||
| 16 | |||
| 17 | /// A resource object which supports blocking until signaled. | ||
| 18 | /// Once finished, the `deinit()` method should be called for correctness. | ||
| 19 | pub const ResetEvent = struct { | ||
| 20 | os_event: OsEvent, | ||
| 21 | |||
| 22 | pub const OsEvent = if (builtin.single_threaded) | ||
| 23 | DebugEvent | ||
| 24 | else if (builtin.link_libc and builtin.os.tag != .windows and builtin.os.tag != .linux) | ||
| 25 | PosixEvent | ||
| 26 | else | ||
| 27 | AtomicEvent; | ||
| 28 | |||
| 29 | pub fn init() ResetEvent { | ||
| 30 | return ResetEvent{ .os_event = OsEvent.init() }; | ||
| 31 | } | ||
| 32 | |||
| 33 | pub fn deinit(self: *ResetEvent) void { | ||
| 34 | self.os_event.deinit(); | ||
| 35 | } | ||
| 36 | |||
| 37 | /// Returns whether or not the event is currenetly set | ||
| 38 | pub fn isSet(self: *ResetEvent) bool { | ||
| 39 | return self.os_event.isSet(); | ||
| 40 | } | ||
| 41 | |||
| 42 | /// Sets the event if not already set and | ||
| 43 | /// wakes up all the threads waiting on the event. | ||
| 44 | pub fn set(self: *ResetEvent) void { | ||
| 45 | return self.os_event.set(); | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Resets the event to its original, unset state. | ||
| 49 | pub fn reset(self: *ResetEvent) void { | ||
| 50 | return self.os_event.reset(); | ||
| 51 | } | ||
| 52 | |||
| 53 | /// Wait for the event to be set by blocking the current thread. | ||
| 54 | pub fn wait(self: *ResetEvent) void { | ||
| 55 | return self.os_event.wait(null) catch unreachable; | ||
| 56 | } | ||
| 57 | |||
| 58 | /// Wait for the event to be set by blocking the current thread. | ||
| 59 | /// A timeout in nanoseconds can be provided as a hint for how | ||
| 60 | /// long the thread should block on the unset event before throwing error.TimedOut. | ||
| 61 | pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void { | ||
| 62 | return self.os_event.wait(timeout_ns); | ||
| 63 | } | ||
| 64 | }; | ||
| 65 | |||
| 66 | const DebugEvent = struct { | ||
| 67 | is_set: bool, | ||
| 68 | |||
| 69 | fn init() DebugEvent { | ||
| 70 | return DebugEvent{ .is_set = false }; | ||
| 71 | } | ||
| 72 | |||
| 73 | fn deinit(self: *DebugEvent) void { | ||
| 74 | self.* = undefined; | ||
| 75 | } | ||
| 76 | |||
| 77 | fn isSet(self: *DebugEvent) bool { | ||
| 78 | return self.is_set; | ||
| 79 | } | ||
| 80 | |||
| 81 | fn reset(self: *DebugEvent) void { | ||
| 82 | self.is_set = false; | ||
| 83 | } | ||
| 84 | |||
| 85 | fn set(self: *DebugEvent) void { | ||
| 86 | self.is_set = true; | ||
| 87 | } | ||
| 88 | |||
| 89 | fn wait(self: *DebugEvent, timeout: ?u64) !void { | ||
| 90 | if (self.is_set) | ||
| 91 | return; | ||
| 92 | if (timeout != null) | ||
| 93 | return error.TimedOut; | ||
| 94 | @panic("deadlock detected"); | ||
| 95 | } | ||
| 96 | }; | ||
| 97 | |||
| 98 | const PosixEvent = struct { | ||
| 99 | is_set: bool, | ||
| 100 | cond: c.pthread_cond_t, | ||
| 101 | mutex: c.pthread_mutex_t, | ||
| 102 | |||
| 103 | fn init() PosixEvent { | ||
| 104 | return PosixEvent{ | ||
| 105 | .is_set = false, | ||
| 106 | .cond = c.PTHREAD_COND_INITIALIZER, | ||
| 107 | .mutex = c.PTHREAD_MUTEX_INITIALIZER, | ||
| 108 | }; | ||
| 109 | } | ||
| 110 | |||
| 111 | fn deinit(self: *PosixEvent) void { | ||
| 112 | // on dragonfly or openbsd, *destroy() functions can return EINVAL | ||
| 113 | // for statically initialized pthread structures | ||
| 114 | const err = if (builtin.os.tag == .dragonfly or builtin.os.tag == .openbsd) | ||
| 115 | os.EINVAL | ||
| 116 | else | ||
| 117 | 0; | ||
| 118 | |||
| 119 | const retm = c.pthread_mutex_destroy(&self.mutex); | ||
| 120 | assert(retm == 0 or retm == err); | ||
| 121 | const retc = c.pthread_cond_destroy(&self.cond); | ||
| 122 | assert(retc == 0 or retc == err); | ||
| 123 | } | ||
| 124 | |||
| 125 | fn isSet(self: *PosixEvent) bool { | ||
| 126 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | ||
| 127 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | ||
| 128 | |||
| 129 | return self.is_set; | ||
| 130 | } | ||
| 131 | |||
| 132 | fn reset(self: *PosixEvent) void { | ||
| 133 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | ||
| 134 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | ||
| 135 | |||
| 136 | self.is_set = false; | ||
| 137 | } | ||
| 138 | |||
| 139 | fn set(self: *PosixEvent) void { | ||
| 140 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | ||
| 141 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | ||
| 142 | |||
| 143 | if (!self.is_set) { | ||
| 144 | self.is_set = true; | ||
| 145 | assert(c.pthread_cond_broadcast(&self.cond) == 0); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | fn wait(self: *PosixEvent, timeout: ?u64) !void { | ||
| 150 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | ||
| 151 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | ||
| 152 | |||
| 153 | // quick guard before possibly calling time syscalls below | ||
| 154 | if (self.is_set) | ||
| 155 | return; | ||
| 156 | |||
| 157 | var ts: os.timespec = undefined; | ||
| 158 | if (timeout) |timeout_ns| { | ||
| 159 | var timeout_abs = timeout_ns; | ||
| 160 | if (comptime std.Target.current.isDarwin()) { | ||
| 161 | var tv: os.darwin.timeval = undefined; | ||
| 162 | assert(os.darwin.gettimeofday(&tv, null) == 0); | ||
| 163 | timeout_abs += @intCast(u64, tv.tv_sec) * time.ns_per_s; | ||
| 164 | timeout_abs += @intCast(u64, tv.tv_usec) * time.ns_per_us; | ||
| 165 | } else { | ||
| 166 | os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable; | ||
| 167 | timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s; | ||
| 168 | timeout_abs += @intCast(u64, ts.tv_nsec); | ||
| 169 | } | ||
| 170 | ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s)); | ||
| 171 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s)); | ||
| 172 | } | ||
| 173 | |||
| 174 | while (!self.is_set) { | ||
| 175 | const rc = switch (timeout == null) { | ||
| 176 | true => c.pthread_cond_wait(&self.cond, &self.mutex), | ||
| 177 | else => c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts), | ||
| 178 | }; | ||
| 179 | switch (rc) { | ||
| 180 | 0 => {}, | ||
| 181 | os.ETIMEDOUT => return error.TimedOut, | ||
| 182 | os.EINVAL => unreachable, | ||
| 183 | os.EPERM => unreachable, | ||
| 184 | else => unreachable, | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | }; | ||
| 189 | |||
| 190 | const AtomicEvent = struct { | ||
| 191 | waiters: u32, | ||
| 192 | |||
| 193 | const WAKE = 1 << 0; | ||
| 194 | const WAIT = 1 << 1; | ||
| 195 | |||
| 196 | fn init() AtomicEvent { | ||
| 197 | return AtomicEvent{ .waiters = 0 }; | ||
| 198 | } | ||
| 199 | |||
| 200 | fn deinit(self: *AtomicEvent) void { | ||
| 201 | self.* = undefined; | ||
| 202 | } | ||
| 203 | |||
| 204 | fn isSet(self: *const AtomicEvent) bool { | ||
| 205 | return @atomicLoad(u32, &self.waiters, .Acquire) == WAKE; | ||
| 206 | } | ||
| 207 | |||
| 208 | fn reset(self: *AtomicEvent) void { | ||
| 209 | @atomicStore(u32, &self.waiters, 0, .Monotonic); | ||
| 210 | } | ||
| 211 | |||
| 212 | fn set(self: *AtomicEvent) void { | ||
| 213 | const waiters = @atomicRmw(u32, &self.waiters, .Xchg, WAKE, .Release); | ||
| 214 | if (waiters >= WAIT) { | ||
| 215 | return Futex.wake(&self.waiters, waiters >> 1); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | fn wait(self: *AtomicEvent, timeout: ?u64) !void { | ||
| 220 | var waiters = @atomicLoad(u32, &self.waiters, .Acquire); | ||
| 221 | while (waiters != WAKE) { | ||
| 222 | waiters = @cmpxchgWeak(u32, &self.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse return Futex.wait(&self.waiters, timeout); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | pub const Futex = switch (builtin.os.tag) { | ||
| 227 | .windows => WindowsFutex, | ||
| 228 | .linux => LinuxFutex, | ||
| 229 | else => SpinFutex, | ||
| 230 | }; | ||
| 231 | |||
| 232 | const SpinFutex = struct { | ||
| 233 | fn wake(waiters: *u32, wake_count: u32) void {} | ||
| 234 | |||
| 235 | fn wait(waiters: *u32, timeout: ?u64) !void { | ||
| 236 | // TODO: handle platforms where a monotonic timer isnt available | ||
| 237 | var timer: time.Timer = undefined; | ||
| 238 | if (timeout != null) | ||
| 239 | timer = time.Timer.start() catch unreachable; | ||
| 240 | |||
| 241 | while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { | ||
| 242 | SpinLock.yield(); | ||
| 243 | if (timeout) |timeout_ns| { | ||
| 244 | if (timer.read() >= timeout_ns) | ||
| 245 | return error.TimedOut; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
| 249 | }; | ||
| 250 | |||
| 251 | const LinuxFutex = struct { | ||
| 252 | fn wake(waiters: *u32, wake_count: u32) void { | ||
| 253 | const waiting = std.math.maxInt(i32); // wake_count | ||
| 254 | const ptr = @ptrCast(*const i32, waiters); | ||
| 255 | const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting); | ||
| 256 | assert(linux.getErrno(rc) == 0); | ||
| 257 | } | ||
| 258 | |||
| 259 | fn wait(waiters: *u32, timeout: ?u64) !void { | ||
| 260 | var ts: linux.timespec = undefined; | ||
| 261 | var ts_ptr: ?*linux.timespec = null; | ||
| 262 | if (timeout) |timeout_ns| { | ||
| 263 | ts_ptr = &ts; | ||
| 264 | ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s); | ||
| 265 | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); | ||
| 266 | } | ||
| 267 | |||
| 268 | while (true) { | ||
| 269 | const waiting = @atomicLoad(u32, waiters, .Acquire); | ||
| 270 | if (waiting == WAKE) | ||
| 271 | return; | ||
| 272 | const expected = @intCast(i32, waiting); | ||
| 273 | const ptr = @ptrCast(*const i32, waiters); | ||
| 274 | const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr); | ||
| 275 | switch (linux.getErrno(rc)) { | ||
| 276 | 0 => continue, | ||
| 277 | os.ETIMEDOUT => return error.TimedOut, | ||
| 278 | os.EINTR => continue, | ||
| 279 | os.EAGAIN => return, | ||
| 280 | else => unreachable, | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | }; | ||
| 285 | |||
| 286 | const WindowsFutex = struct { | ||
| 287 | pub fn wake(waiters: *u32, wake_count: u32) void { | ||
| 288 | const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count); | ||
| 289 | const key = @ptrCast(*const c_void, waiters); | ||
| 290 | |||
| 291 | var waiting = wake_count; | ||
| 292 | while (waiting != 0) : (waiting -= 1) { | ||
| 293 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); | ||
| 294 | assert(rc == .SUCCESS); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | pub fn wait(waiters: *u32, timeout: ?u64) !void { | ||
| 299 | const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout); | ||
| 300 | const key = @ptrCast(*const c_void, waiters); | ||
| 301 | |||
| 302 | // NT uses timeouts in units of 100ns with negative value being relative | ||
| 303 | var timeout_ptr: ?*windows.LARGE_INTEGER = null; | ||
| 304 | var timeout_value: windows.LARGE_INTEGER = undefined; | ||
| 305 | if (timeout) |timeout_ns| { | ||
| 306 | timeout_ptr = &timeout_value; | ||
| 307 | timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100); | ||
| 308 | } | ||
| 309 | |||
| 310 | // NtWaitForKeyedEvent doesnt have spurious wake-ups | ||
| 311 | var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); | ||
| 312 | switch (rc) { | ||
| 313 | .TIMEOUT => { | ||
| 314 | // update the wait count to signal that we're not waiting anymore. | ||
| 315 | // if the .set() thread already observed that we are, perform a | ||
| 316 | // matching NtWaitForKeyedEvent so that the .set() thread doesn't | ||
| 317 | // deadlock trying to run NtReleaseKeyedEvent above. | ||
| 318 | var waiting = @atomicLoad(u32, waiters, .Monotonic); | ||
| 319 | while (true) { | ||
| 320 | if (waiting == WAKE) { | ||
| 321 | rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); | ||
| 322 | assert(rc == .WAIT_0); | ||
| 323 | break; | ||
| 324 | } else { | ||
| 325 | waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break; | ||
| 326 | continue; | ||
| 327 | } | ||
| 328 | } | ||
| 329 | return error.TimedOut; | ||
| 330 | }, | ||
| 331 | .WAIT_0 => {}, | ||
| 332 | else => unreachable, | ||
| 333 | } | ||
| 334 | } | ||
| 335 | |||
| 336 | var event_handle: usize = EMPTY; | ||
| 337 | const EMPTY = ~@as(usize, 0); | ||
| 338 | const LOADING = EMPTY - 1; | ||
| 339 | |||
| 340 | pub fn getEventHandle() ?windows.HANDLE { | ||
| 341 | var handle = @atomicLoad(usize, &event_handle, .Monotonic); | ||
| 342 | while (true) { | ||
| 343 | switch (handle) { | ||
| 344 | EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse { | ||
| 345 | const handle_ptr = @ptrCast(*windows.HANDLE, &handle); | ||
| 346 | const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; | ||
| 347 | if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS) | ||
| 348 | handle = 0; | ||
| 349 | @atomicStore(usize, &event_handle, handle, .Monotonic); | ||
| 350 | return @intToPtr(?windows.HANDLE, handle); | ||
| 351 | }, | ||
| 352 | LOADING => { | ||
| 353 | SpinLock.yield(); | ||
| 354 | handle = @atomicLoad(usize, &event_handle, .Monotonic); | ||
| 355 | }, | ||
| 356 | else => { | ||
| 357 | return @intToPtr(?windows.HANDLE, handle); | ||
| 358 | }, | ||
| 359 | } | ||
| 360 | } | ||
| 361 | } | ||
| 362 | }; | ||
| 363 | }; | ||
| 364 | |||
| 365 | test "ResetEvent" { | ||
| 366 | var event = ResetEvent.init(); | ||
| 367 | defer event.deinit(); | ||
| 368 | |||
| 369 | // test event setting | ||
| 370 | testing.expect(event.isSet() == false); | ||
| 371 | event.set(); | ||
| 372 | testing.expect(event.isSet() == true); | ||
| 373 | |||
| 374 | // test event resetting | ||
| 375 | event.reset(); | ||
| 376 | testing.expect(event.isSet() == false); | ||
| 377 | |||
| 378 | // test event waiting (non-blocking) | ||
| 379 | event.set(); | ||
| 380 | event.wait(); | ||
| 381 | try event.timedWait(1); | ||
| 382 | |||
| 383 | // test cross-thread signaling | ||
| 384 | if (builtin.single_threaded) | ||
| 385 | return; | ||
| 386 | |||
| 387 | const Context = struct { | ||
| 388 | const Self = @This(); | ||
| 389 | |||
| 390 | value: u128, | ||
| 391 | in: ResetEvent, | ||
| 392 | out: ResetEvent, | ||
| 393 | |||
| 394 | fn init() Self { | ||
| 395 | return Self{ | ||
| 396 | .value = 0, | ||
| 397 | .in = ResetEvent.init(), | ||
| 398 | .out = ResetEvent.init(), | ||
| 399 | }; | ||
| 400 | } | ||
| 401 | |||
| 402 | fn deinit(self: *Self) void { | ||
| 403 | self.in.deinit(); | ||
| 404 | self.out.deinit(); | ||
| 405 | self.* = undefined; | ||
| 406 | } | ||
| 407 | |||
| 408 | fn sender(self: *Self) void { | ||
| 409 | // update value and signal input | ||
| 410 | testing.expect(self.value == 0); | ||
| 411 | self.value = 1; | ||
| 412 | self.in.set(); | ||
| 413 | |||
| 414 | // wait for receiver to update value and signal output | ||
| 415 | self.out.wait(); | ||
| 416 | testing.expect(self.value == 2); | ||
| 417 | |||
| 418 | // update value and signal final input | ||
| 419 | self.value = 3; | ||
| 420 | self.in.set(); | ||
| 421 | } | ||
| 422 | |||
| 423 | fn receiver(self: *Self) void { | ||
| 424 | // wait for sender to update value and signal input | ||
| 425 | self.in.wait(); | ||
| 426 | assert(self.value == 1); | ||
| 427 | |||
| 428 | // update value and signal output | ||
| 429 | self.in.reset(); | ||
| 430 | self.value = 2; | ||
| 431 | self.out.set(); | ||
| 432 | |||
| 433 | // wait for sender to update value and signal final input | ||
| 434 | self.in.wait(); | ||
| 435 | assert(self.value == 3); | ||
| 436 | } | ||
| 437 | |||
| 438 | fn sleeper(self: *Self) void { | ||
| 439 | self.in.set(); | ||
| 440 | time.sleep(time.ns_per_ms * 2); | ||
| 441 | self.value = 5; | ||
| 442 | self.out.set(); | ||
| 443 | } | ||
| 444 | |||
| 445 | fn timedWaiter(self: *Self) !void { | ||
| 446 | self.in.wait(); | ||
| 447 | testing.expectError(error.TimedOut, self.out.timedWait(time.ns_per_us)); | ||
| 448 | try self.out.timedWait(time.ns_per_ms * 100); | ||
| 449 | testing.expect(self.value == 5); | ||
| 450 | } | ||
| 451 | }; | ||
| 452 | |||
| 453 | var context = Context.init(); | ||
| 454 | defer context.deinit(); | ||
| 455 | const receiver = try std.Thread.spawn(&context, Context.receiver); | ||
| 456 | defer receiver.wait(); | ||
| 457 | context.sender(); | ||
| 458 | |||
| 459 | if (false) { | ||
| 460 | // I have now observed this fail on macOS, Windows, and Linux. | ||
| 461 | // https://github.com/ziglang/zig/issues/7009 | ||
| 462 | var timed = Context.init(); | ||
| 463 | defer timed.deinit(); | ||
| 464 | const sleeper = try std.Thread.spawn(&timed, Context.sleeper); | ||
| 465 | defer sleeper.wait(); | ||
| 466 | try timed.timedWaiter(); | ||
| 467 | } | ||
| 468 | } | ||
lib/std/std.zig+2-1| ... | @@ -30,10 +30,11 @@ pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; | ... | @@ -30,10 +30,11 @@ pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; |
| 30 | pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; | 30 | pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; |
| 31 | pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; | 31 | pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; |
| 32 | pub const Progress = @import("Progress.zig"); | 32 | pub const Progress = @import("Progress.zig"); |
| 33 | pub const ResetEvent = @import("reset_event.zig").ResetEvent; | 33 | pub const ResetEvent = @import("ResetEvent.zig"); |
| 34 | pub const SemanticVersion = @import("SemanticVersion.zig"); | 34 | pub const SemanticVersion = @import("SemanticVersion.zig"); |
| 35 | pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; | 35 | pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; |
| 36 | pub const SpinLock = @import("spinlock.zig").SpinLock; | 36 | pub const SpinLock = @import("spinlock.zig").SpinLock; |
| 37 | pub const StaticResetEvent = @import("StaticResetEvent.zig"); | ||
| 37 | pub const StringHashMap = hash_map.StringHashMap; | 38 | pub const StringHashMap = hash_map.StringHashMap; |
| 38 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; | 39 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; |
| 39 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; | 40 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; |
src/Compilation.zig+13-5| ... | @@ -135,6 +135,8 @@ emit_docs: ?EmitLoc, | ... | @@ -135,6 +135,8 @@ emit_docs: ?EmitLoc, |
| 135 | 135 | ||
| 136 | c_header: ?c_link.Header, | 136 | c_header: ?c_link.Header, |
| 137 | 137 | ||
| 138 | work_queue_wait_group: WaitGroup, | ||
| 139 | |||
| 138 | pub const InnerError = Module.InnerError; | 140 | pub const InnerError = Module.InnerError; |
| 139 | 141 | ||
| 140 | pub const CRTFile = struct { | 142 | pub const CRTFile = struct { |
| ... | @@ -1006,11 +1008,15 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { | ... | @@ -1006,11 +1008,15 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 1006 | .test_filter = options.test_filter, | 1008 | .test_filter = options.test_filter, |
| 1007 | .test_name_prefix = options.test_name_prefix, | 1009 | .test_name_prefix = options.test_name_prefix, |
| 1008 | .test_evented_io = options.test_evented_io, | 1010 | .test_evented_io = options.test_evented_io, |
| 1011 | .work_queue_wait_group = undefined, | ||
| 1009 | }; | 1012 | }; |
| 1010 | break :comp comp; | 1013 | break :comp comp; |
| 1011 | }; | 1014 | }; |
| 1012 | errdefer comp.destroy(); | 1015 | errdefer comp.destroy(); |
| 1013 | 1016 | ||
| 1017 | try comp.work_queue_wait_group.init(); | ||
| 1018 | errdefer comp.work_queue_wait_group.deinit(); | ||
| 1019 | |||
| 1014 | if (comp.bin_file.options.module) |mod| { | 1020 | if (comp.bin_file.options.module) |mod| { |
| 1015 | try comp.work_queue.writeItem(.{ .generate_builtin_zig = {} }); | 1021 | try comp.work_queue.writeItem(.{ .generate_builtin_zig = {} }); |
| 1016 | } | 1022 | } |
| ... | @@ -1191,6 +1197,8 @@ pub fn destroy(self: *Compilation) void { | ... | @@ -1191,6 +1197,8 @@ pub fn destroy(self: *Compilation) void { |
| 1191 | self.cache_parent.manifest_dir.close(); | 1197 | self.cache_parent.manifest_dir.close(); |
| 1192 | if (self.owned_link_dir) |*dir| dir.close(); | 1198 | if (self.owned_link_dir) |*dir| dir.close(); |
| 1193 | 1199 | ||
| 1200 | self.work_queue_wait_group.deinit(); | ||
| 1201 | |||
| 1194 | // This destroys `self`. | 1202 | // This destroys `self`. |
| 1195 | self.arena_state.promote(gpa).deinit(); | 1203 | self.arena_state.promote(gpa).deinit(); |
| 1196 | } | 1204 | } |
| ... | @@ -1405,13 +1413,13 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor | ... | @@ -1405,13 +1413,13 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor |
| 1405 | var arena = std.heap.ArenaAllocator.init(self.gpa); | 1413 | var arena = std.heap.ArenaAllocator.init(self.gpa); |
| 1406 | defer arena.deinit(); | 1414 | defer arena.deinit(); |
| 1407 | 1415 | ||
| 1408 | var wg = WaitGroup{}; | 1416 | self.work_queue_wait_group.reset(); |
| 1409 | defer wg.wait(); | 1417 | defer self.work_queue_wait_group.wait(); |
| 1410 | 1418 | ||
| 1411 | while (self.c_object_work_queue.readItem()) |c_object| { | 1419 | while (self.c_object_work_queue.readItem()) |c_object| { |
| 1412 | wg.start(); | 1420 | self.work_queue_wait_group.start(); |
| 1413 | try self.thread_pool.spawn(workerUpdateCObject, .{ | 1421 | try self.thread_pool.spawn(workerUpdateCObject, .{ |
| 1414 | self, c_object, &c_comp_progress_node, &wg, | 1422 | self, c_object, &c_comp_progress_node, &self.work_queue_wait_group, |
| 1415 | }); | 1423 | }); |
| 1416 | } | 1424 | } |
| 1417 | 1425 | ||
| ... | @@ -1764,7 +1772,7 @@ fn workerUpdateCObject( | ... | @@ -1764,7 +1772,7 @@ fn workerUpdateCObject( |
| 1764 | progress_node: *std.Progress.Node, | 1772 | progress_node: *std.Progress.Node, |
| 1765 | wg: *WaitGroup, | 1773 | wg: *WaitGroup, |
| 1766 | ) void { | 1774 | ) void { |
| 1767 | defer wg.stop(); | 1775 | defer wg.finish(); |
| 1768 | 1776 | ||
| 1769 | comp.updateCObject(c_object, progress_node) catch |err| switch (err) { | 1777 | comp.updateCObject(c_object, progress_node) catch |err| switch (err) { |
| 1770 | error.AnalysisFail => return, | 1778 | error.AnalysisFail => return, |
src/Event.zig deleted-43| ... | @@ -1,43 +0,0 @@ | ||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | // Copyright (c) 2015-2020 Zig Contributors | ||
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| 4 | // The MIT license requires this copyright notice to be included in all copies | ||
| 5 | // and substantial portions of the software. | ||
| 6 | const std = @import("std"); | ||
| 7 | const Event = @This(); | ||
| 8 | |||
| 9 | lock: std.Mutex = .{}, | ||
| 10 | event: std.ResetEvent = undefined, | ||
| 11 | state: enum { empty, waiting, notified } = .empty, | ||
| 12 | |||
| 13 | pub fn wait(self: *Event) void { | ||
| 14 | const held = self.lock.acquire(); | ||
| 15 | |||
| 16 | switch (self.state) { | ||
| 17 | .empty => { | ||
| 18 | self.state = .waiting; | ||
| 19 | self.event = @TypeOf(self.event).init(); | ||
| 20 | held.release(); | ||
| 21 | self.event.wait(); | ||
| 22 | self.event.deinit(); | ||
| 23 | }, | ||
| 24 | .waiting => unreachable, | ||
| 25 | .notified => held.release(), | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn set(self: *Event) void { | ||
| 30 | const held = self.lock.acquire(); | ||
| 31 | |||
| 32 | switch (self.state) { | ||
| 33 | .empty => { | ||
| 34 | self.state = .notified; | ||
| 35 | held.release(); | ||
| 36 | }, | ||
| 37 | .waiting => { | ||
| 38 | held.release(); | ||
| 39 | self.event.set(); | ||
| 40 | }, | ||
| 41 | .notified => unreachable, | ||
| 42 | } | ||
| 43 | } | ||
src/ThreadPool.zig+74-62| ... | @@ -9,70 +9,102 @@ const ThreadPool = @This(); | ... | @@ -9,70 +9,102 @@ const ThreadPool = @This(); |
| 9 | lock: std.Mutex = .{}, | 9 | lock: std.Mutex = .{}, |
| 10 | is_running: bool = true, | 10 | is_running: bool = true, |
| 11 | allocator: *std.mem.Allocator, | 11 | allocator: *std.mem.Allocator, |
| 12 | running: usize = 0, | 12 | workers: []Worker, |
| 13 | threads: []*std.Thread, | ||
| 14 | run_queue: RunQueue = .{}, | 13 | run_queue: RunQueue = .{}, |
| 15 | idle_queue: IdleQueue = .{}, | 14 | idle_queue: IdleQueue = .{}, |
| 16 | 15 | ||
| 17 | const IdleQueue = std.SinglyLinkedList(std.AutoResetEvent); | 16 | const IdleQueue = std.SinglyLinkedList(std.ResetEvent); |
| 18 | const RunQueue = std.SinglyLinkedList(Runnable); | 17 | const RunQueue = std.SinglyLinkedList(Runnable); |
| 19 | const Runnable = struct { | 18 | const Runnable = struct { |
| 20 | runFn: fn (*Runnable) void, | 19 | runFn: fn (*Runnable) void, |
| 21 | }; | 20 | }; |
| 22 | 21 | ||
| 22 | const Worker = struct { | ||
| 23 | pool: *ThreadPool, | ||
| 24 | thread: *std.Thread, | ||
| 25 | /// The node is for this worker only and must have an already initialized event | ||
| 26 | /// when the thread is spawned. | ||
| 27 | idle_node: IdleQueue.Node, | ||
| 28 | |||
| 29 | fn run(worker: *Worker) void { | ||
| 30 | while (true) { | ||
| 31 | const held = worker.pool.lock.acquire(); | ||
| 32 | |||
| 33 | if (worker.pool.run_queue.popFirst()) |run_node| { | ||
| 34 | held.release(); | ||
| 35 | (run_node.data.runFn)(&run_node.data); | ||
| 36 | continue; | ||
| 37 | } | ||
| 38 | |||
| 39 | if (worker.pool.is_running) { | ||
| 40 | worker.idle_node.data.reset(); | ||
| 41 | |||
| 42 | worker.pool.idle_queue.prepend(&worker.idle_node); | ||
| 43 | held.release(); | ||
| 44 | |||
| 45 | worker.idle_node.data.wait(); | ||
| 46 | continue; | ||
| 47 | } | ||
| 48 | |||
| 49 | held.release(); | ||
| 50 | return; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 23 | pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void { | 55 | pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void { |
| 24 | self.* = .{ | 56 | self.* = .{ |
| 25 | .allocator = allocator, | 57 | .allocator = allocator, |
| 26 | .threads = &[_]*std.Thread{}, | 58 | .workers = &[_]Worker{}, |
| 27 | }; | 59 | }; |
| 28 | if (std.builtin.single_threaded) | 60 | if (std.builtin.single_threaded) |
| 29 | return; | 61 | return; |
| 30 | 62 | ||
| 31 | errdefer self.deinit(); | 63 | const worker_count = std.math.max(1, std.Thread.cpuCount() catch 1); |
| 64 | self.workers = try allocator.alloc(Worker, worker_count); | ||
| 65 | errdefer allocator.free(self.workers); | ||
| 66 | |||
| 67 | var worker_index: usize = 0; | ||
| 68 | errdefer self.destroyWorkers(worker_index); | ||
| 69 | while (worker_index < worker_count) : (worker_index += 1) { | ||
| 70 | const worker = &self.workers[worker_index]; | ||
| 71 | worker.pool = self; | ||
| 32 | 72 | ||
| 33 | var num_threads = std.Thread.cpuCount() catch 1; | 73 | // Each worker requires its ResetEvent to be pre-initialized. |
| 34 | if (num_threads > 0) | 74 | try worker.idle_node.data.init(); |
| 35 | self.threads = try allocator.alloc(*std.Thread, num_threads); | 75 | errdefer worker.idle_node.data.deinit(); |
| 36 | 76 | ||
| 37 | while (num_threads > 0) : (num_threads -= 1) { | 77 | worker.thread = try std.Thread.spawn(worker, Worker.run); |
| 38 | const thread = try std.Thread.spawn(self, runWorker); | ||
| 39 | self.threads[self.running] = thread; | ||
| 40 | self.running += 1; | ||
| 41 | } | 78 | } |
| 42 | } | 79 | } |
| 43 | 80 | ||
| 44 | pub fn deinit(self: *ThreadPool) void { | 81 | fn destroyWorkers(self: *ThreadPool, spawned: usize) void { |
| 45 | self.shutdown(); | 82 | for (self.workers[0..spawned]) |*worker| { |
| 46 | 83 | worker.thread.wait(); | |
| 47 | std.debug.assert(!self.is_running); | 84 | worker.idle_node.data.deinit(); |
| 48 | for (self.threads[0..self.running]) |thread| | 85 | } |
| 49 | thread.wait(); | ||
| 50 | |||
| 51 | defer self.threads = &[_]*std.Thread{}; | ||
| 52 | if (self.running > 0) | ||
| 53 | self.allocator.free(self.threads); | ||
| 54 | } | 86 | } |
| 55 | 87 | ||
| 56 | pub fn shutdown(self: *ThreadPool) void { | 88 | pub fn deinit(self: *ThreadPool) void { |
| 57 | const held = self.lock.acquire(); | 89 | { |
| 58 | 90 | const held = self.lock.acquire(); | |
| 59 | if (!self.is_running) | 91 | defer held.release(); |
| 60 | return held.release(); | ||
| 61 | 92 | ||
| 62 | var idle_queue = self.idle_queue; | 93 | self.is_running = false; |
| 63 | self.idle_queue = .{}; | 94 | while (self.idle_queue.popFirst()) |idle_node| |
| 64 | self.is_running = false; | 95 | idle_node.data.set(); |
| 65 | held.release(); | 96 | } |
| 66 | 97 | ||
| 67 | while (idle_queue.popFirst()) |idle_node| | 98 | self.destroyWorkers(self.workers.len); |
| 68 | idle_node.data.set(); | 99 | self.allocator.free(self.workers); |
| 69 | } | 100 | } |
| 70 | 101 | ||
| 71 | pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { | 102 | pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| 72 | if (std.builtin.single_threaded) { | 103 | if (std.builtin.single_threaded) { |
| 73 | @call(.{}, func, args); | 104 | const result = @call(.{}, func, args); |
| 74 | return; | 105 | return; |
| 75 | } | 106 | } |
| 107 | |||
| 76 | const Args = @TypeOf(args); | 108 | const Args = @TypeOf(args); |
| 77 | const Closure = struct { | 109 | const Closure = struct { |
| 78 | arguments: Args, | 110 | arguments: Args, |
| ... | @@ -83,44 +115,24 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { | ... | @@ -83,44 +115,24 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| 83 | const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable); | 115 | const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable); |
| 84 | const closure = @fieldParentPtr(@This(), "run_node", run_node); | 116 | const closure = @fieldParentPtr(@This(), "run_node", run_node); |
| 85 | const result = @call(.{}, func, closure.arguments); | 117 | const result = @call(.{}, func, closure.arguments); |
| 118 | |||
| 119 | const held = closure.pool.lock.acquire(); | ||
| 120 | defer held.release(); | ||
| 86 | closure.pool.allocator.destroy(closure); | 121 | closure.pool.allocator.destroy(closure); |
| 87 | } | 122 | } |
| 88 | }; | 123 | }; |
| 89 | 124 | ||
| 125 | const held = self.lock.acquire(); | ||
| 126 | defer held.release(); | ||
| 127 | |||
| 90 | const closure = try self.allocator.create(Closure); | 128 | const closure = try self.allocator.create(Closure); |
| 91 | closure.* = .{ | 129 | closure.* = .{ |
| 92 | .arguments = args, | 130 | .arguments = args, |
| 93 | .pool = self, | 131 | .pool = self, |
| 94 | }; | 132 | }; |
| 95 | 133 | ||
| 96 | const held = self.lock.acquire(); | ||
| 97 | self.run_queue.prepend(&closure.run_node); | 134 | self.run_queue.prepend(&closure.run_node); |
| 98 | 135 | ||
| 99 | const idle_node = self.idle_queue.popFirst(); | 136 | if (self.idle_queue.popFirst()) |idle_node| |
| 100 | held.release(); | 137 | idle_node.data.set(); |
| 101 | |||
| 102 | if (idle_node) |node| | ||
| 103 | node.data.set(); | ||
| 104 | } | ||
| 105 | |||
| 106 | fn runWorker(self: *ThreadPool) void { | ||
| 107 | while (true) { | ||
| 108 | const held = self.lock.acquire(); | ||
| 109 | |||
| 110 | if (self.run_queue.popFirst()) |run_node| { | ||
| 111 | held.release(); | ||
| 112 | (run_node.data.runFn)(&run_node.data); | ||
| 113 | continue; | ||
| 114 | } | ||
| 115 | |||
| 116 | if (!self.is_running) { | ||
| 117 | held.release(); | ||
| 118 | return; | ||
| 119 | } | ||
| 120 | |||
| 121 | var idle_node = IdleQueue.Node{ .data = .{} }; | ||
| 122 | self.idle_queue.prepend(&idle_node); | ||
| 123 | held.release(); | ||
| 124 | idle_node.data.wait(); | ||
| 125 | } | ||
| 126 | } | 138 | } |
src/WaitGroup.zig+33-18| ... | @@ -5,11 +5,24 @@ | ... | @@ -5,11 +5,24 @@ |
| 5 | // and substantial portions of the software. | 5 | // and substantial portions of the software. |
| 6 | const std = @import("std"); | 6 | const std = @import("std"); |
| 7 | const WaitGroup = @This(); | 7 | const WaitGroup = @This(); |
| 8 | const Event = @import("Event.zig"); | ||
| 9 | 8 | ||
| 10 | lock: std.Mutex = .{}, | 9 | lock: std.Mutex = .{}, |
| 11 | counter: usize = 0, | 10 | counter: usize = 0, |
| 12 | event: ?*Event = null, | 11 | event: std.ResetEvent, |
| 12 | |||
| 13 | pub fn init(self: *WaitGroup) !void { | ||
| 14 | self.* = .{ | ||
| 15 | .lock = .{}, | ||
| 16 | .counter = 0, | ||
| 17 | .event = undefined, | ||
| 18 | }; | ||
| 19 | try self.event.init(); | ||
| 20 | } | ||
| 21 | |||
| 22 | pub fn deinit(self: *WaitGroup) void { | ||
| 23 | self.event.deinit(); | ||
| 24 | self.* = undefined; | ||
| 25 | } | ||
| 13 | 26 | ||
| 14 | pub fn start(self: *WaitGroup) void { | 27 | pub fn start(self: *WaitGroup) void { |
| 15 | const held = self.lock.acquire(); | 28 | const held = self.lock.acquire(); |
| ... | @@ -18,29 +31,31 @@ pub fn start(self: *WaitGroup) void { | ... | @@ -18,29 +31,31 @@ pub fn start(self: *WaitGroup) void { |
| 18 | self.counter += 1; | 31 | self.counter += 1; |
| 19 | } | 32 | } |
| 20 | 33 | ||
| 21 | pub fn stop(self: *WaitGroup) void { | 34 | pub fn finish(self: *WaitGroup) void { |
| 22 | var event: ?*Event = null; | ||
| 23 | defer if (event) |waiter| | ||
| 24 | waiter.set(); | ||
| 25 | |||
| 26 | const held = self.lock.acquire(); | 35 | const held = self.lock.acquire(); |
| 27 | defer held.release(); | 36 | defer held.release(); |
| 28 | 37 | ||
| 29 | self.counter -= 1; | 38 | self.counter -= 1; |
| 30 | if (self.counter == 0) | 39 | |
| 31 | std.mem.swap(?*Event, &self.event, &event); | 40 | if (self.counter == 0) { |
| 41 | self.event.set(); | ||
| 42 | } | ||
| 32 | } | 43 | } |
| 33 | 44 | ||
| 34 | pub fn wait(self: *WaitGroup) void { | 45 | pub fn wait(self: *WaitGroup) void { |
| 35 | var event = Event{}; | 46 | while (true) { |
| 36 | var has_event = false; | 47 | const held = self.lock.acquire(); |
| 37 | defer if (has_event) | ||
| 38 | event.wait(); | ||
| 39 | 48 | ||
| 40 | const held = self.lock.acquire(); | 49 | if (self.counter == 0) { |
| 41 | defer held.release(); | 50 | held.release(); |
| 51 | return; | ||
| 52 | } | ||
| 53 | |||
| 54 | held.release(); | ||
| 55 | self.event.wait(); | ||
| 56 | } | ||
| 57 | } | ||
| 42 | 58 | ||
| 43 | has_event = self.counter != 0; | 59 | pub fn reset(self: *WaitGroup) void { |
| 44 | if (has_event) | 60 | self.event.reset(); |
| 45 | self.event = &event; | ||
| 46 | } | 61 | } |