| author | |
| committer | |
| log | 177377b6e356b34bbed40cadca596658d158af6b |
| tree | ed7e0a7fa146b8c15044e21f386ec8a8e2977695 |
| parent | 5377b7fb97311448daa3c29a8c8f100656d871ba |
* split std.ResetEvent into:
- ResetEvent - requires init() at runtime and it can fail. Also
requires deinit().
- StaticResetEvent - can be statically initialized and requires no
deinitialization. Initialization cannot fail.
* the POSIX sem_t implementation can in fact fail on initialization
because it is allowed to be implemented as a file descriptor.
* Completely define, clarify, and explain in detail the semantics of
these APIs. Remove the `isSet` function.
* `ResetEvent.timedWait` returns an enum instead of a possible error.
* `ResetEvent.init` takes a pointer to the ResetEvent instead of
returning a copy.
* On Darwin, `ResetEvent` is implemented using Grand Central Dispatch,
which is exposed by libSystem.
stage2 changes:
* ThreadPool: use a single, pre-initialized `ResetEvent` per worker.
* WaitGroup: now requires init() and deinit() and init() can fail.
- Add a `reset` function.
- Compilation initializes one for the work queue in creation and
re-uses it for every update.
- Rename `stop` to `finish`.
- Simplify the implementation based on the usage pattern.12 files changed, 805 insertions(+), 567 deletions(-)
CMakeLists.txt+3-2| ... | ... | @@ -410,11 +410,12 @@ set(ZIG_STAGE2_SOURCES |
| 410 | 410 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/bits.zig" |
| 411 | 411 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" |
| 412 | 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 | 416 | "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig" |
| 414 | 417 | "${CMAKE_SOURCE_DIR}/lib/std/process.zig" |
| 415 | "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig" | |
| 416 | 418 | "${CMAKE_SOURCE_DIR}/lib/std/rand.zig" |
| 417 | "${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig" | |
| 418 | 419 | "${CMAKE_SOURCE_DIR}/lib/std/sort.zig" |
| 419 | 420 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt.zig" |
| 420 | 421 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/addXf3.zig" |
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 | 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/c/darwin.zig+12| ... | ... | @@ -187,3 +187,15 @@ pub const pthread_attr_t = extern struct { |
| 187 | 187 | }; |
| 188 | 188 | |
| 189 | 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/debug.zig+1-2| ... | ... | @@ -274,9 +274,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 274 | 274 | // and call abort() |
| 275 | 275 | |
| 276 | 276 | // Sleep forever without hammering the CPU |
| 277 | var event = std.ResetEvent.init(); | |
| 277 | var event: std.StaticResetEvent = .{}; | |
| 278 | 278 | event.wait(); |
| 279 | ||
| 280 | 279 | unreachable; |
| 281 | 280 | } |
| 282 | 281 | }, |
lib/std/fs/test.zig-2| ... | ... | @@ -771,8 +771,6 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 771 | 771 | std.time.sleep(SLEEP_TIMEOUT_NS); |
| 772 | 772 | if (timer.read() >= SLEEP_TIMEOUT_NS) break; |
| 773 | 773 | } |
| 774 | // Check that createFile is still waiting for the lock to be released. | |
| 775 | testing.expect(!evt.isSet()); | |
| 776 | 774 | file.close(); |
| 777 | 775 | // No timeout to avoid failures on heavily loaded systems. |
| 778 | 776 | evt.wait(); |
lib/std/mutex.zig+2-2| ... | ... | @@ -284,7 +284,7 @@ const WindowsMutex = struct { |
| 284 | 284 | fn acquireSlow(self: *WindowsMutex) Held { |
| 285 | 285 | // try to use NT keyed events for blocking, falling back to spinlock if unavailable |
| 286 | 286 | @setCold(true); |
| 287 | const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning(); | |
| 287 | const handle = ResetEvent.Impl.Futex.getEventHandle() orelse return self.acquireSpinning(); | |
| 288 | 288 | const key = @ptrCast(*const c_void, &self.state.waiters); |
| 289 | 289 | |
| 290 | 290 | while (true) : (SpinLock.loopHint(1)) { |
| ... | ... | @@ -312,7 +312,7 @@ const WindowsMutex = struct { |
| 312 | 312 | pub fn release(self: Held) void { |
| 313 | 313 | // unlock without a rmw/cmpxchg instruction |
| 314 | 314 | @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release); |
| 315 | const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return; | |
| 315 | const handle = ResetEvent.Impl.Futex.getEventHandle() orelse return; | |
| 316 | 316 | const key = @ptrCast(*const c_void, &self.mutex.state.waiters); |
| 317 | 317 | |
| 318 | 318 | while (true) : (SpinLock.loopHint(1)) { |
lib/std/reset_event.zig deleted-501| ... | ... | @@ -1,501 +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 (std.Thread.use_pthreads) | |
| 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 | /// When `wait` would return without blocking, this returns `true`. | |
| 38 | /// Note that the value may be immediately invalid upon this function's | |
| 39 | /// return, because another thread may call `wait` in between, changing | |
| 40 | /// the event's set/cleared status. | |
| 41 | pub fn isSet(self: *ResetEvent) bool { | |
| 42 | return self.os_event.isSet(); | |
| 43 | } | |
| 44 | ||
| 45 | /// Sets the event if not already set and | |
| 46 | /// wakes up all the threads waiting on the event. | |
| 47 | pub fn set(self: *ResetEvent) void { | |
| 48 | return self.os_event.set(); | |
| 49 | } | |
| 50 | ||
| 51 | /// Resets the event to its original, unset state. | |
| 52 | /// TODO improve these docs: | |
| 53 | /// * under what circumstances does it make sense to call this function? | |
| 54 | pub fn reset(self: *ResetEvent) void { | |
| 55 | return self.os_event.reset(); | |
| 56 | } | |
| 57 | ||
| 58 | /// Wait for the event to be set by blocking the current thread. | |
| 59 | /// TODO improve these docs: | |
| 60 | /// * is the function thread-safe? | |
| 61 | /// * does it have suprious wakeups? | |
| 62 | pub fn wait(self: *ResetEvent) void { | |
| 63 | return self.os_event.wait(); | |
| 64 | } | |
| 65 | ||
| 66 | /// Wait for the event to be set by blocking the current thread. | |
| 67 | /// A timeout in nanoseconds can be provided as a hint for how | |
| 68 | /// long the thread should block on the unset event before throwing error.TimedOut. | |
| 69 | /// TODO improve these docs: | |
| 70 | /// * is the function thread-safe? | |
| 71 | /// * does it have suprious wakeups? | |
| 72 | pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void { | |
| 73 | return self.os_event.timedWait(timeout_ns); | |
| 74 | } | |
| 75 | }; | |
| 76 | ||
| 77 | const DebugEvent = struct { | |
| 78 | is_set: bool, | |
| 79 | ||
| 80 | fn init() DebugEvent { | |
| 81 | return DebugEvent{ .is_set = false }; | |
| 82 | } | |
| 83 | ||
| 84 | fn deinit(self: *DebugEvent) void { | |
| 85 | self.* = undefined; | |
| 86 | } | |
| 87 | ||
| 88 | fn isSet(self: *DebugEvent) bool { | |
| 89 | return self.is_set; | |
| 90 | } | |
| 91 | ||
| 92 | fn reset(self: *DebugEvent) void { | |
| 93 | self.is_set = false; | |
| 94 | } | |
| 95 | ||
| 96 | fn set(self: *DebugEvent) void { | |
| 97 | self.is_set = true; | |
| 98 | } | |
| 99 | ||
| 100 | fn wait(self: *DebugEvent) void { | |
| 101 | if (self.is_set) | |
| 102 | return; | |
| 103 | ||
| 104 | @panic("deadlock detected"); | |
| 105 | } | |
| 106 | ||
| 107 | fn timedWait(self: *DebugEvent, timeout: u64) !void { | |
| 108 | if (self.is_set) | |
| 109 | return; | |
| 110 | ||
| 111 | return error.TimedOut; | |
| 112 | } | |
| 113 | }; | |
| 114 | ||
| 115 | const PosixEvent = struct { | |
| 116 | sem: c.sem_t = undefined, | |
| 117 | /// Sadly this is needed because pthreads semaphore API does not | |
| 118 | /// support static initialization. | |
| 119 | init_mutex: std.mutex.PthreadMutex = .{}, | |
| 120 | state: enum { uninit, init } = .uninit, | |
| 121 | ||
| 122 | fn init() PosixEvent { | |
| 123 | return .{}; | |
| 124 | } | |
| 125 | ||
| 126 | /// Not thread-safe. | |
| 127 | fn deinit(self: *PosixEvent) void { | |
| 128 | switch (self.state) { | |
| 129 | .uninit => {}, | |
| 130 | .init => { | |
| 131 | assert(c.sem_destroy(&self.sem) == 0); | |
| 132 | }, | |
| 133 | } | |
| 134 | self.* = undefined; | |
| 135 | } | |
| 136 | ||
| 137 | fn isSet(self: *PosixEvent) bool { | |
| 138 | const sem = self.getInitializedSem(); | |
| 139 | var val: c_int = undefined; | |
| 140 | assert(c.sem_getvalue(sem, &val) == 0); | |
| 141 | return val > 0; | |
| 142 | } | |
| 143 | ||
| 144 | fn reset(self: *PosixEvent) void { | |
| 145 | const sem = self.getInitializedSem(); | |
| 146 | while (true) { | |
| 147 | switch (c.getErrno(c.sem_trywait(sem))) { | |
| 148 | 0 => continue, // Need to make it go to zero. | |
| 149 | c.EINTR => continue, | |
| 150 | c.EINVAL => unreachable, | |
| 151 | c.EAGAIN => return, // The semaphore currently has the value zero. | |
| 152 | else => unreachable, | |
| 153 | } | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 157 | fn set(self: *PosixEvent) void { | |
| 158 | const sem = self.getInitializedSem(); | |
| 159 | assert(c.sem_post(sem) == 0); | |
| 160 | } | |
| 161 | ||
| 162 | fn wait(self: *PosixEvent) void { | |
| 163 | const sem = self.getInitializedSem(); | |
| 164 | while (true) { | |
| 165 | switch (c.getErrno(c.sem_wait(sem))) { | |
| 166 | 0 => return, | |
| 167 | c.EINTR => continue, | |
| 168 | c.EINVAL => unreachable, | |
| 169 | else => unreachable, | |
| 170 | } | |
| 171 | } | |
| 172 | } | |
| 173 | ||
| 174 | fn timedWait(self: *PosixEvent, timeout_ns: u64) !void { | |
| 175 | var ts: os.timespec = undefined; | |
| 176 | var timeout_abs = timeout_ns; | |
| 177 | if (comptime std.Target.current.isDarwin()) { | |
| 178 | var tv: os.darwin.timeval = undefined; | |
| 179 | assert(os.darwin.gettimeofday(&tv, null) == 0); | |
| 180 | timeout_abs += @intCast(u64, tv.tv_sec) * time.ns_per_s; | |
| 181 | timeout_abs += @intCast(u64, tv.tv_usec) * time.ns_per_us; | |
| 182 | } else { | |
| 183 | os.clock_gettime(os.CLOCK_REALTIME, &ts) catch return error.TimedOut; | |
| 184 | timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s; | |
| 185 | timeout_abs += @intCast(u64, ts.tv_nsec); | |
| 186 | } | |
| 187 | ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s)); | |
| 188 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s)); | |
| 189 | const sem = self.getInitializedSem(); | |
| 190 | while (true) { | |
| 191 | switch (c.getErrno(c.sem_timedwait(&self.sem, &ts))) { | |
| 192 | 0 => return, | |
| 193 | c.EINTR => continue, | |
| 194 | c.EINVAL => unreachable, | |
| 195 | c.ETIMEDOUT => return error.TimedOut, | |
| 196 | else => unreachable, | |
| 197 | } | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | fn getInitializedSem(self: *PosixEvent) *c.sem_t { | |
| 202 | const held = self.init_mutex.acquire(); | |
| 203 | defer held.release(); | |
| 204 | ||
| 205 | switch (self.state) { | |
| 206 | .init => return &self.sem, | |
| 207 | .uninit => { | |
| 208 | self.state = .init; | |
| 209 | assert(c.sem_init(&self.sem, 0, 0) == 0); | |
| 210 | return &self.sem; | |
| 211 | }, | |
| 212 | } | |
| 213 | } | |
| 214 | }; | |
| 215 | ||
| 216 | const AtomicEvent = struct { | |
| 217 | waiters: u32, | |
| 218 | ||
| 219 | const WAKE = 1 << 0; | |
| 220 | const WAIT = 1 << 1; | |
| 221 | ||
| 222 | fn init() AtomicEvent { | |
| 223 | return AtomicEvent{ .waiters = 0 }; | |
| 224 | } | |
| 225 | ||
| 226 | fn deinit(self: *AtomicEvent) void { | |
| 227 | self.* = undefined; | |
| 228 | } | |
| 229 | ||
| 230 | fn isSet(self: *const AtomicEvent) bool { | |
| 231 | return @atomicLoad(u32, &self.waiters, .Acquire) == WAKE; | |
| 232 | } | |
| 233 | ||
| 234 | fn reset(self: *AtomicEvent) void { | |
| 235 | @atomicStore(u32, &self.waiters, 0, .Monotonic); | |
| 236 | } | |
| 237 | ||
| 238 | fn set(self: *AtomicEvent) void { | |
| 239 | const waiters = @atomicRmw(u32, &self.waiters, .Xchg, WAKE, .Release); | |
| 240 | if (waiters >= WAIT) { | |
| 241 | return Futex.wake(&self.waiters, waiters >> 1); | |
| 242 | } | |
| 243 | } | |
| 244 | ||
| 245 | fn wait(self: *AtomicEvent) void { | |
| 246 | return self.timedWait(null) catch unreachable; | |
| 247 | } | |
| 248 | ||
| 249 | fn timedWait(self: *AtomicEvent, timeout: ?u64) !void { | |
| 250 | var waiters = @atomicLoad(u32, &self.waiters, .Acquire); | |
| 251 | while (waiters != WAKE) { | |
| 252 | waiters = @cmpxchgWeak(u32, &self.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse return Futex.wait(&self.waiters, timeout); | |
| 253 | } | |
| 254 | } | |
| 255 | ||
| 256 | pub const Futex = switch (builtin.os.tag) { | |
| 257 | .windows => WindowsFutex, | |
| 258 | .linux => LinuxFutex, | |
| 259 | else => SpinFutex, | |
| 260 | }; | |
| 261 | ||
| 262 | const SpinFutex = struct { | |
| 263 | fn wake(waiters: *u32, wake_count: u32) void {} | |
| 264 | ||
| 265 | fn wait(waiters: *u32, timeout: ?u64) !void { | |
| 266 | // TODO: handle platforms where a monotonic timer isnt available | |
| 267 | var timer: time.Timer = undefined; | |
| 268 | if (timeout != null) | |
| 269 | timer = time.Timer.start() catch unreachable; | |
| 270 | ||
| 271 | while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { | |
| 272 | SpinLock.yield(); | |
| 273 | if (timeout) |timeout_ns| { | |
| 274 | if (timer.read() >= timeout_ns) | |
| 275 | return error.TimedOut; | |
| 276 | } | |
| 277 | } | |
| 278 | } | |
| 279 | }; | |
| 280 | ||
| 281 | const LinuxFutex = struct { | |
| 282 | fn wake(waiters: *u32, wake_count: u32) void { | |
| 283 | const waiting = std.math.maxInt(i32); // wake_count | |
| 284 | const ptr = @ptrCast(*const i32, waiters); | |
| 285 | const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting); | |
| 286 | assert(linux.getErrno(rc) == 0); | |
| 287 | } | |
| 288 | ||
| 289 | fn wait(waiters: *u32, timeout: ?u64) !void { | |
| 290 | var ts: linux.timespec = undefined; | |
| 291 | var ts_ptr: ?*linux.timespec = null; | |
| 292 | if (timeout) |timeout_ns| { | |
| 293 | ts_ptr = &ts; | |
| 294 | ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s); | |
| 295 | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); | |
| 296 | } | |
| 297 | ||
| 298 | while (true) { | |
| 299 | const waiting = @atomicLoad(u32, waiters, .Acquire); | |
| 300 | if (waiting == WAKE) | |
| 301 | return; | |
| 302 | const expected = @intCast(i32, waiting); | |
| 303 | const ptr = @ptrCast(*const i32, waiters); | |
| 304 | const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr); | |
| 305 | switch (linux.getErrno(rc)) { | |
| 306 | 0 => continue, | |
| 307 | os.ETIMEDOUT => return error.TimedOut, | |
| 308 | os.EINTR => continue, | |
| 309 | os.EAGAIN => return, | |
| 310 | else => unreachable, | |
| 311 | } | |
| 312 | } | |
| 313 | } | |
| 314 | }; | |
| 315 | ||
| 316 | const WindowsFutex = struct { | |
| 317 | pub fn wake(waiters: *u32, wake_count: u32) void { | |
| 318 | const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count); | |
| 319 | const key = @ptrCast(*const c_void, waiters); | |
| 320 | ||
| 321 | var waiting = wake_count; | |
| 322 | while (waiting != 0) : (waiting -= 1) { | |
| 323 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); | |
| 324 | assert(rc == .SUCCESS); | |
| 325 | } | |
| 326 | } | |
| 327 | ||
| 328 | pub fn wait(waiters: *u32, timeout: ?u64) !void { | |
| 329 | const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout); | |
| 330 | const key = @ptrCast(*const c_void, waiters); | |
| 331 | ||
| 332 | // NT uses timeouts in units of 100ns with negative value being relative | |
| 333 | var timeout_ptr: ?*windows.LARGE_INTEGER = null; | |
| 334 | var timeout_value: windows.LARGE_INTEGER = undefined; | |
| 335 | if (timeout) |timeout_ns| { | |
| 336 | timeout_ptr = &timeout_value; | |
| 337 | timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100); | |
| 338 | } | |
| 339 | ||
| 340 | // NtWaitForKeyedEvent doesnt have spurious wake-ups | |
| 341 | var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); | |
| 342 | switch (rc) { | |
| 343 | .TIMEOUT => { | |
| 344 | // update the wait count to signal that we're not waiting anymore. | |
| 345 | // if the .set() thread already observed that we are, perform a | |
| 346 | // matching NtWaitForKeyedEvent so that the .set() thread doesn't | |
| 347 | // deadlock trying to run NtReleaseKeyedEvent above. | |
| 348 | var waiting = @atomicLoad(u32, waiters, .Monotonic); | |
| 349 | while (true) { | |
| 350 | if (waiting == WAKE) { | |
| 351 | rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); | |
| 352 | assert(rc == .WAIT_0); | |
| 353 | break; | |
| 354 | } else { | |
| 355 | waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break; | |
| 356 | continue; | |
| 357 | } | |
| 358 | } | |
| 359 | return error.TimedOut; | |
| 360 | }, | |
| 361 | .WAIT_0 => {}, | |
| 362 | else => unreachable, | |
| 363 | } | |
| 364 | } | |
| 365 | ||
| 366 | var event_handle: usize = EMPTY; | |
| 367 | const EMPTY = ~@as(usize, 0); | |
| 368 | const LOADING = EMPTY - 1; | |
| 369 | ||
| 370 | pub fn getEventHandle() ?windows.HANDLE { | |
| 371 | var handle = @atomicLoad(usize, &event_handle, .Monotonic); | |
| 372 | while (true) { | |
| 373 | switch (handle) { | |
| 374 | EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse { | |
| 375 | const handle_ptr = @ptrCast(*windows.HANDLE, &handle); | |
| 376 | const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; | |
| 377 | if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS) | |
| 378 | handle = 0; | |
| 379 | @atomicStore(usize, &event_handle, handle, .Monotonic); | |
| 380 | return @intToPtr(?windows.HANDLE, handle); | |
| 381 | }, | |
| 382 | LOADING => { | |
| 383 | SpinLock.yield(); | |
| 384 | handle = @atomicLoad(usize, &event_handle, .Monotonic); | |
| 385 | }, | |
| 386 | else => { | |
| 387 | return @intToPtr(?windows.HANDLE, handle); | |
| 388 | }, | |
| 389 | } | |
| 390 | } | |
| 391 | } | |
| 392 | }; | |
| 393 | }; | |
| 394 | ||
| 395 | test "ResetEvent" { | |
| 396 | var event = ResetEvent.init(); | |
| 397 | defer event.deinit(); | |
| 398 | ||
| 399 | // test event setting | |
| 400 | testing.expect(!event.isSet()); | |
| 401 | event.set(); | |
| 402 | testing.expect(event.isSet()); | |
| 403 | ||
| 404 | // test event resetting | |
| 405 | event.reset(); | |
| 406 | testing.expect(!event.isSet()); | |
| 407 | ||
| 408 | // test event waiting (non-blocking) | |
| 409 | event.set(); | |
| 410 | event.wait(); | |
| 411 | event.reset(); | |
| 412 | ||
| 413 | event.set(); | |
| 414 | try event.timedWait(1); | |
| 415 | ||
| 416 | // test cross-thread signaling | |
| 417 | if (builtin.single_threaded) | |
| 418 | return; | |
| 419 | ||
| 420 | const Context = struct { | |
| 421 | const Self = @This(); | |
| 422 | ||
| 423 | value: u128, | |
| 424 | in: ResetEvent, | |
| 425 | out: ResetEvent, | |
| 426 | ||
| 427 | fn init() Self { | |
| 428 | return Self{ | |
| 429 | .value = 0, | |
| 430 | .in = ResetEvent.init(), | |
| 431 | .out = ResetEvent.init(), | |
| 432 | }; | |
| 433 | } | |
| 434 | ||
| 435 | fn deinit(self: *Self) void { | |
| 436 | self.in.deinit(); | |
| 437 | self.out.deinit(); | |
| 438 | self.* = undefined; | |
| 439 | } | |
| 440 | ||
| 441 | fn sender(self: *Self) void { | |
| 442 | // update value and signal input | |
| 443 | testing.expect(self.value == 0); | |
| 444 | self.value = 1; | |
| 445 | self.in.set(); | |
| 446 | ||
| 447 | // wait for receiver to update value and signal output | |
| 448 | self.out.wait(); | |
| 449 | testing.expect(self.value == 2); | |
| 450 | ||
| 451 | // update value and signal final input | |
| 452 | self.value = 3; | |
| 453 | self.in.set(); | |
| 454 | } | |
| 455 | ||
| 456 | fn receiver(self: *Self) void { | |
| 457 | // wait for sender to update value and signal input | |
| 458 | self.in.wait(); | |
| 459 | assert(self.value == 1); | |
| 460 | ||
| 461 | // update value and signal output | |
| 462 | self.in.reset(); | |
| 463 | self.value = 2; | |
| 464 | self.out.set(); | |
| 465 | ||
| 466 | // wait for sender to update value and signal final input | |
| 467 | self.in.wait(); | |
| 468 | assert(self.value == 3); | |
| 469 | } | |
| 470 | ||
| 471 | fn sleeper(self: *Self) void { | |
| 472 | self.in.set(); | |
| 473 | time.sleep(time.ns_per_ms * 2); | |
| 474 | self.value = 5; | |
| 475 | self.out.set(); | |
| 476 | } | |
| 477 | ||
| 478 | fn timedWaiter(self: *Self) !void { | |
| 479 | self.in.wait(); | |
| 480 | testing.expectError(error.TimedOut, self.out.timedWait(time.ns_per_us)); | |
| 481 | try self.out.timedWait(time.ns_per_ms * 100); | |
| 482 | testing.expect(self.value == 5); | |
| 483 | } | |
| 484 | }; | |
| 485 | ||
| 486 | var context = Context.init(); | |
| 487 | defer context.deinit(); | |
| 488 | const receiver = try std.Thread.spawn(&context, Context.receiver); | |
| 489 | defer receiver.wait(); | |
| 490 | context.sender(); | |
| 491 | ||
| 492 | if (false) { | |
| 493 | // I have now observed this fail on macOS, Windows, and Linux. | |
| 494 | // https://github.com/ziglang/zig/issues/7009 | |
| 495 | var timed = Context.init(); | |
| 496 | defer timed.deinit(); | |
| 497 | const sleeper = try std.Thread.spawn(&timed, Context.sleeper); | |
| 498 | defer sleeper.wait(); | |
| 499 | try timed.timedWaiter(); | |
| 500 | } | |
| 501 | } |
lib/std/std.zig+2-1| ... | ... | @@ -30,10 +30,11 @@ pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; |
| 30 | 30 | pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; |
| 31 | 31 | pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; |
| 32 | 32 | pub const Progress = @import("Progress.zig"); |
| 33 | pub const ResetEvent = @import("reset_event.zig").ResetEvent; | |
| 33 | pub const ResetEvent = @import("ResetEvent.zig"); | |
| 34 | 34 | pub const SemanticVersion = @import("SemanticVersion.zig"); |
| 35 | 35 | pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; |
| 36 | 36 | pub const SpinLock = @import("spinlock.zig").SpinLock; |
| 37 | pub const StaticResetEvent = @import("StaticResetEvent.zig"); | |
| 37 | 38 | pub const StringHashMap = hash_map.StringHashMap; |
| 38 | 39 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; |
| 39 | 40 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; |
src/Compilation.zig+13-5| ... | ... | @@ -135,6 +135,8 @@ emit_docs: ?EmitLoc, |
| 135 | 135 | |
| 136 | 136 | c_header: ?c_link.Header, |
| 137 | 137 | |
| 138 | work_queue_wait_group: WaitGroup, | |
| 139 | ||
| 138 | 140 | pub const InnerError = Module.InnerError; |
| 139 | 141 | |
| 140 | 142 | pub const CRTFile = struct { |
| ... | ... | @@ -1006,11 +1008,15 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 1006 | 1008 | .test_filter = options.test_filter, |
| 1007 | 1009 | .test_name_prefix = options.test_name_prefix, |
| 1008 | 1010 | .test_evented_io = options.test_evented_io, |
| 1011 | .work_queue_wait_group = undefined, | |
| 1009 | 1012 | }; |
| 1010 | 1013 | break :comp comp; |
| 1011 | 1014 | }; |
| 1012 | 1015 | errdefer comp.destroy(); |
| 1013 | 1016 | |
| 1017 | try comp.work_queue_wait_group.init(); | |
| 1018 | errdefer comp.work_queue_wait_group.deinit(); | |
| 1019 | ||
| 1014 | 1020 | if (comp.bin_file.options.module) |mod| { |
| 1015 | 1021 | try comp.work_queue.writeItem(.{ .generate_builtin_zig = {} }); |
| 1016 | 1022 | } |
| ... | ... | @@ -1191,6 +1197,8 @@ pub fn destroy(self: *Compilation) void { |
| 1191 | 1197 | self.cache_parent.manifest_dir.close(); |
| 1192 | 1198 | if (self.owned_link_dir) |*dir| dir.close(); |
| 1193 | 1199 | |
| 1200 | self.work_queue_wait_group.deinit(); | |
| 1201 | ||
| 1194 | 1202 | // This destroys `self`. |
| 1195 | 1203 | self.arena_state.promote(gpa).deinit(); |
| 1196 | 1204 | } |
| ... | ... | @@ -1405,13 +1413,13 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor |
| 1405 | 1413 | var arena = std.heap.ArenaAllocator.init(self.gpa); |
| 1406 | 1414 | defer arena.deinit(); |
| 1407 | 1415 | |
| 1408 | var wg = WaitGroup{}; | |
| 1409 | defer wg.wait(); | |
| 1416 | self.work_queue_wait_group.reset(); | |
| 1417 | defer self.work_queue_wait_group.wait(); | |
| 1410 | 1418 | |
| 1411 | 1419 | while (self.c_object_work_queue.readItem()) |c_object| { |
| 1412 | wg.start(); | |
| 1420 | self.work_queue_wait_group.start(); | |
| 1413 | 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 | 1772 | progress_node: *std.Progress.Node, |
| 1765 | 1773 | wg: *WaitGroup, |
| 1766 | 1774 | ) void { |
| 1767 | defer wg.stop(); | |
| 1775 | defer wg.finish(); | |
| 1768 | 1776 | |
| 1769 | 1777 | comp.updateCObject(c_object, progress_node) catch |err| switch (err) { |
| 1770 | 1778 | error.AnalysisFail => return, |
src/ThreadPool.zig+57-39| ... | ... | @@ -9,8 +9,7 @@ const ThreadPool = @This(); |
| 9 | 9 | lock: std.Mutex = .{}, |
| 10 | 10 | is_running: bool = true, |
| 11 | 11 | allocator: *std.mem.Allocator, |
| 12 | spawned: usize = 0, | |
| 13 | threads: []*std.Thread, | |
| 12 | workers: []Worker, | |
| 14 | 13 | run_queue: RunQueue = .{}, |
| 15 | 14 | idle_queue: IdleQueue = .{}, |
| 16 | 15 | |
| ... | ... | @@ -20,23 +19,69 @@ const Runnable = struct { |
| 20 | 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 | 55 | pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void { |
| 24 | 56 | self.* = .{ |
| 25 | 57 | .allocator = allocator, |
| 26 | .threads = &[_]*std.Thread{}, | |
| 58 | .workers = &[_]Worker{}, | |
| 27 | 59 | }; |
| 28 | 60 | if (std.builtin.single_threaded) |
| 29 | 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.math.max(1, std.Thread.cpuCount() catch 1); | |
| 34 | self.threads = try allocator.alloc(*std.Thread, num_threads); | |
| 73 | // Each worker requires its ResetEvent to be pre-initialized. | |
| 74 | try worker.idle_node.data.init(); | |
| 75 | errdefer worker.idle_node.data.deinit(); | |
| 76 | ||
| 77 | worker.thread = try std.Thread.spawn(worker, Worker.run); | |
| 78 | } | |
| 79 | } | |
| 35 | 80 | |
| 36 | while (num_threads > 0) : (num_threads -= 1) { | |
| 37 | const thread = try std.Thread.spawn(self, runWorker); | |
| 38 | self.threads[self.spawned] = thread; | |
| 39 | self.spawned += 1; | |
| 81 | fn destroyWorkers(self: *ThreadPool, spawned: usize) void { | |
| 82 | for (self.workers[0..spawned]) |*worker| { | |
| 83 | worker.thread.wait(); | |
| 84 | worker.idle_node.data.deinit(); | |
| 40 | 85 | } |
| 41 | 86 | } |
| 42 | 87 | |
| ... | ... | @@ -50,9 +95,8 @@ pub fn deinit(self: *ThreadPool) void { |
| 50 | 95 | idle_node.data.set(); |
| 51 | 96 | } |
| 52 | 97 | |
| 53 | defer self.allocator.free(self.threads); | |
| 54 | for (self.threads[0..self.spawned]) |thread| | |
| 55 | thread.wait(); | |
| 98 | self.destroyWorkers(self.workers.len); | |
| 99 | self.allocator.free(self.workers); | |
| 56 | 100 | } |
| 57 | 101 | |
| 58 | 102 | pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| ... | ... | @@ -92,29 +136,3 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| 92 | 136 | if (self.idle_queue.popFirst()) |idle_node| |
| 93 | 137 | idle_node.data.set(); |
| 94 | 138 | } |
| 95 | ||
| 96 | fn runWorker(self: *ThreadPool) void { | |
| 97 | while (true) { | |
| 98 | const held = self.lock.acquire(); | |
| 99 | ||
| 100 | if (self.run_queue.popFirst()) |run_node| { | |
| 101 | held.release(); | |
| 102 | (run_node.data.runFn)(&run_node.data); | |
| 103 | continue; | |
| 104 | } | |
| 105 | ||
| 106 | if (self.is_running) { | |
| 107 | var idle_node = IdleQueue.Node{ .data = std.ResetEvent.init() }; | |
| 108 | ||
| 109 | self.idle_queue.prepend(&idle_node); | |
| 110 | held.release(); | |
| 111 | ||
| 112 | idle_node.data.wait(); | |
| 113 | idle_node.data.deinit(); | |
| 114 | continue; | |
| 115 | } | |
| 116 | ||
| 117 | held.release(); | |
| 118 | return; | |
| 119 | } | |
| 120 | } |
src/WaitGroup.zig+22-13| ... | ... | @@ -8,7 +8,21 @@ const WaitGroup = @This(); |
| 8 | 8 | |
| 9 | 9 | lock: std.Mutex = .{}, |
| 10 | 10 | counter: usize = 0, |
| 11 | event: ?*std.ResetEvent = 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 | } | |
| 12 | 26 | |
| 13 | 27 | pub fn start(self: *WaitGroup) void { |
| 14 | 28 | const held = self.lock.acquire(); |
| ... | ... | @@ -17,17 +31,14 @@ pub fn start(self: *WaitGroup) void { |
| 17 | 31 | self.counter += 1; |
| 18 | 32 | } |
| 19 | 33 | |
| 20 | pub fn stop(self: *WaitGroup) void { | |
| 34 | pub fn finish(self: *WaitGroup) void { | |
| 21 | 35 | const held = self.lock.acquire(); |
| 22 | 36 | defer held.release(); |
| 23 | 37 | |
| 24 | 38 | self.counter -= 1; |
| 25 | 39 | |
| 26 | 40 | if (self.counter == 0) { |
| 27 | if (self.event) |event| { | |
| 28 | self.event = null; | |
| 29 | event.set(); | |
| 30 | } | |
| 41 | self.event.set(); | |
| 31 | 42 | } |
| 32 | 43 | } |
| 33 | 44 | |
| ... | ... | @@ -40,13 +51,11 @@ pub fn wait(self: *WaitGroup) void { |
| 40 | 51 | return; |
| 41 | 52 | } |
| 42 | 53 | |
| 43 | var event = std.ResetEvent.init(); | |
| 44 | defer event.deinit(); | |
| 45 | ||
| 46 | std.debug.assert(self.event == null); | |
| 47 | self.event = &event; | |
| 48 | ||
| 49 | 54 | held.release(); |
| 50 | event.wait(); | |
| 55 | self.event.wait(); | |
| 51 | 56 | } |
| 52 | 57 | } |
| 58 | ||
| 59 | pub fn reset(self: *WaitGroup) void { | |
| 60 | self.event.reset(); | |
| 61 | } |