| 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 | 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" |
| ... | ... | @@ -512,7 +513,6 @@ set(ZIG_STAGE2_SOURCES |
| 512 | 513 | "${CMAKE_SOURCE_DIR}/src/Cache.zig" |
| 513 | 514 | "${CMAKE_SOURCE_DIR}/src/Compilation.zig" |
| 514 | 515 | "${CMAKE_SOURCE_DIR}/src/DepTokenizer.zig" |
| 515 | "${CMAKE_SOURCE_DIR}/src/Event.zig" | |
| 516 | 516 | "${CMAKE_SOURCE_DIR}/src/Module.zig" |
| 517 | 517 | "${CMAKE_SOURCE_DIR}/src/Package.zig" |
| 518 | 518 | "${CMAKE_SOURCE_DIR}/src/RangeSet.zig" |
lib/std/Progress.zig+3| ... | ... | @@ -160,6 +160,9 @@ pub fn maybeRefresh(self: *Progress) void { |
| 160 | 160 | if (now < self.initial_delay_ns) return; |
| 161 | 161 | const held = self.update_lock.tryAcquire() orelse return; |
| 162 | 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 | 166 | if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; |
| 164 | 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 | 7 | const builtin = @import("builtin"); |
| 8 | 8 | const testing = std.testing; |
| 9 | 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 | /// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like). | |
| 12 | /// Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`. | |
| 13 | /// Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like). | |
| 13 | 14 | pub const AutoResetEvent = struct { |
| 14 | 15 | /// AutoResetEvent has 3 possible states: |
| 15 | 16 | /// - UNSET: the AutoResetEvent is currently unset |
| 16 | 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 | 20 | /// When attempting to wait: |
| 20 | 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 | 26 | /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent |
| 26 | 27 | /// |
| 27 | 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 | /// thread 1 | thread 2 | |
| 30 | /// std.ResetEvent.wait() | | |
| 31 | /// | std.ResetEvent.set() | |
| 32 | /// | std.ResetEvent.set() | |
| 33 | /// std.ResetEvent.reset() | | |
| 34 | /// std.ResetEvent.wait() | (missed the second .set() notification above) | |
| 29 | /// and avoids the race condition when using StaticResetEvent in the following scenario: | |
| 30 | /// thread 1 | thread 2 | |
| 31 | /// StaticResetEvent.wait() | | |
| 32 | /// | StaticResetEvent.set() | |
| 33 | /// | StaticResetEvent.set() | |
| 34 | /// StaticResetEvent.reset() | | |
| 35 | /// StaticResetEvent.wait() | (missed the second .set() notification above) | |
| 35 | 36 | state: usize = UNSET, |
| 36 | 37 | |
| 37 | 38 | const UNSET = 0; |
| 38 | 39 | const SET = 1; |
| 39 | 40 | |
| 40 | /// the minimum alignment for the `*std.ResetEvent` created by wait*() | |
| 41 | const event_align = std.math.max(@alignOf(std.ResetEvent), 2); | |
| 41 | /// the minimum alignment for the `*StaticResetEvent` created by wait*() | |
| 42 | const event_align = std.math.max(@alignOf(StaticResetEvent), 2); | |
| 42 | 43 | |
| 43 | 44 | pub fn wait(self: *AutoResetEvent) void { |
| 44 | 45 | self.waitFor(null) catch unreachable; |
| ... | ... | @@ -49,12 +50,9 @@ pub const AutoResetEvent = struct { |
| 49 | 50 | } |
| 50 | 51 | |
| 51 | 52 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { |
| 52 | // lazily initialized std.ResetEvent | |
| 53 | var reset_event: std.ResetEvent align(event_align) = undefined; | |
| 53 | // lazily initialized StaticResetEvent | |
| 54 | var reset_event: StaticResetEvent align(event_align) = undefined; | |
| 54 | 55 | var has_reset_event = false; |
| 55 | defer if (has_reset_event) { | |
| 56 | reset_event.deinit(); | |
| 57 | }; | |
| 58 | 56 | |
| 59 | 57 | var state = @atomicLoad(usize, &self.state, .SeqCst); |
| 60 | 58 | while (true) { |
| ... | ... | @@ -72,7 +70,7 @@ pub const AutoResetEvent = struct { |
| 72 | 70 | // lazily initialize the ResetEvent if it hasn't been already |
| 73 | 71 | if (!has_reset_event) { |
| 74 | 72 | has_reset_event = true; |
| 75 | reset_event = std.ResetEvent.init(); | |
| 73 | reset_event = .{}; | |
| 76 | 74 | } |
| 77 | 75 | |
| 78 | 76 | // Since the AutoResetEvent currently isnt set, |
| ... | ... | @@ -97,9 +95,10 @@ pub const AutoResetEvent = struct { |
| 97 | 95 | }; |
| 98 | 96 | |
| 99 | 97 | // wait with a timeout and return if signalled via set() |
| 100 | if (reset_event.timedWait(timeout_ns)) |_| { | |
| 101 | return; | |
| 102 | } else |timed_out| {} | |
| 98 | switch (reset_event.timedWait(timeout_ns)) { | |
| 99 | .event_set => return, | |
| 100 | .timed_out => {}, | |
| 101 | } | |
| 103 | 102 | |
| 104 | 103 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. |
| 105 | 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 | 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 | 167 | reset_event.set(); |
| 169 | 168 | return; |
| 170 | 169 | } |
lib/std/c.zig+8| ... | ... | @@ -270,6 +270,13 @@ pub extern "c" fn pthread_atfork( |
| 270 | 270 | parent: ?fn () callconv(.C) void, |
| 271 | 271 | child: ?fn () callconv(.C) void, |
| 272 | 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 | 281 | pub extern "c" fn kqueue() c_int; |
| 275 | 282 | pub extern "c" fn kevent( |
| ... | ... | @@ -316,6 +323,7 @@ pub extern "c" fn dn_expand( |
| 316 | 323 | pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{}; |
| 317 | 324 | pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) c_int; |
| 318 | 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 | 327 | pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int; |
| 320 | 328 | |
| 321 | 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 | 177 | __sig: c_long = 0x3CB0B1BB, |
| 178 | 178 | __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__, |
| 179 | 179 | }; |
| 180 | pub const sem_t = c_int; | |
| 180 | 181 | const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40; |
| 181 | 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 | 187 | }; |
| 187 | 188 | |
| 188 | 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 | 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 | 59 | pub const EAI = extern enum(c_int) { |
| 51 | 60 | /// address family for hostname not supported |
| 52 | 61 | ADDRFAMILY = 1, |
lib/std/c/linux.zig+5| ... | ... | @@ -123,6 +123,10 @@ pub const pthread_mutex_t = extern struct { |
| 123 | 123 | pub const pthread_cond_t = extern struct { |
| 124 | 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 | 130 | const __SIZEOF_PTHREAD_COND_T = 48; |
| 127 | 131 | const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) { |
| 128 | 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 | 138 | }, |
| 135 | 139 | else => unreachable, |
| 136 | 140 | }; |
| 141 | const __SIZEOF_SEM_T = 4 * @sizeOf(usize); | |
| 137 | 142 | |
| 138 | 143 | pub const RTLD_LAZY = 1; |
| 139 | 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 | 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-3| ... | ... | @@ -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 | 763 | defer evt.deinit(); |
| 763 | 764 | |
| 764 | 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 | 772 | std.time.sleep(SLEEP_TIMEOUT_NS); |
| 772 | 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 | 775 | file.close(); |
| 777 | 776 | // No timeout to avoid failures on heavily loaded systems. |
| 778 | 777 | evt.wait(); |
lib/std/mutex.zig+57-8| ... | ... | @@ -10,7 +10,7 @@ const assert = std.debug.assert; |
| 10 | 10 | const windows = os.windows; |
| 11 | 11 | const testing = std.testing; |
| 12 | 12 | const SpinLock = std.SpinLock; |
| 13 | const ResetEvent = std.ResetEvent; | |
| 13 | const StaticResetEvent = std.StaticResetEvent; | |
| 14 | 14 | |
| 15 | 15 | /// Lock may be held only once. If the same thread tries to acquire |
| 16 | 16 | /// the same mutex twice, it deadlocks. This type supports static |
| ... | ... | @@ -37,6 +37,8 @@ pub const Mutex = if (builtin.single_threaded) |
| 37 | 37 | Dummy |
| 38 | 38 | else if (builtin.os.tag == .windows) |
| 39 | 39 | WindowsMutex |
| 40 | else if (std.Thread.use_pthreads) | |
| 41 | PthreadMutex | |
| 40 | 42 | else if (builtin.link_libc or builtin.os.tag == .linux) |
| 41 | 43 | // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs |
| 42 | 44 | struct { |
| ... | ... | @@ -52,7 +54,7 @@ else if (builtin.link_libc or builtin.os.tag == .linux) |
| 52 | 54 | |
| 53 | 55 | const Node = struct { |
| 54 | 56 | next: ?*Node, |
| 55 | event: ResetEvent, | |
| 57 | event: StaticResetEvent, | |
| 56 | 58 | }; |
| 57 | 59 | |
| 58 | 60 | pub fn tryAcquire(self: *Mutex) ?Held { |
| ... | ... | @@ -88,11 +90,12 @@ else if (builtin.link_libc or builtin.os.tag == .linux) |
| 88 | 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 | 94 | // (faster than threadlocal on platforms like OSX) |
| 93 | var node: Node = undefined; | |
| 94 | node.event = ResetEvent.init(); | |
| 95 | defer node.event.deinit(); | |
| 95 | var node: Node = .{ | |
| 96 | .next = undefined, | |
| 97 | .event = .{}, | |
| 98 | }; | |
| 96 | 99 | |
| 97 | 100 | // we've spun too long, try and add our node to the LIFO queue. |
| 98 | 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 | 169 | else |
| 167 | 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 | 218 | /// This has the sematics as `Mutex`, however it does not actually do any |
| 170 | 219 | /// synchronization. Operations are safety-checked no-ops. |
| 171 | 220 | pub const Dummy = struct { |
| ... | ... | @@ -236,7 +285,7 @@ const WindowsMutex = struct { |
| 236 | 285 | fn acquireSlow(self: *WindowsMutex) Held { |
| 237 | 286 | // try to use NT keyed events for blocking, falling back to spinlock if unavailable |
| 238 | 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 | 289 | const key = @ptrCast(*const c_void, &self.state.waiters); |
| 241 | 290 | |
| 242 | 291 | while (true) : (SpinLock.loopHint(1)) { |
| ... | ... | @@ -264,7 +313,7 @@ const WindowsMutex = struct { |
| 264 | 313 | pub fn release(self: Held) void { |
| 265 | 314 | // unlock without a rmw/cmpxchg instruction |
| 266 | 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 | 317 | const key = @ptrCast(*const c_void, &self.mutex.state.waiters); |
| 269 | 318 | |
| 270 | 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 | 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/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 | 9 | lock: std.Mutex = .{}, |
| 10 | 10 | is_running: bool = true, |
| 11 | 11 | allocator: *std.mem.Allocator, |
| 12 | running: usize = 0, | |
| 13 | threads: []*std.Thread, | |
| 12 | workers: []Worker, | |
| 14 | 13 | run_queue: RunQueue = .{}, |
| 15 | 14 | idle_queue: IdleQueue = .{}, |
| 16 | 15 | |
| 17 | const IdleQueue = std.SinglyLinkedList(std.AutoResetEvent); | |
| 16 | const IdleQueue = std.SinglyLinkedList(std.ResetEvent); | |
| 18 | 17 | const RunQueue = std.SinglyLinkedList(Runnable); |
| 19 | 18 | 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.Thread.cpuCount() catch 1; | |
| 34 | if (num_threads > 0) | |
| 35 | 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(); | |
| 36 | 76 | |
| 37 | while (num_threads > 0) : (num_threads -= 1) { | |
| 38 | const thread = try std.Thread.spawn(self, runWorker); | |
| 39 | self.threads[self.running] = thread; | |
| 40 | self.running += 1; | |
| 77 | worker.thread = try std.Thread.spawn(worker, Worker.run); | |
| 41 | 78 | } |
| 42 | 79 | } |
| 43 | 80 | |
| 44 | pub fn deinit(self: *ThreadPool) void { | |
| 45 | self.shutdown(); | |
| 46 | ||
| 47 | std.debug.assert(!self.is_running); | |
| 48 | for (self.threads[0..self.running]) |thread| | |
| 49 | thread.wait(); | |
| 50 | ||
| 51 | defer self.threads = &[_]*std.Thread{}; | |
| 52 | if (self.running > 0) | |
| 53 | self.allocator.free(self.threads); | |
| 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(); | |
| 85 | } | |
| 54 | 86 | } |
| 55 | 87 | |
| 56 | pub fn shutdown(self: *ThreadPool) void { | |
| 57 | const held = self.lock.acquire(); | |
| 58 | ||
| 59 | if (!self.is_running) | |
| 60 | return held.release(); | |
| 88 | pub fn deinit(self: *ThreadPool) void { | |
| 89 | { | |
| 90 | const held = self.lock.acquire(); | |
| 91 | defer held.release(); | |
| 61 | 92 | |
| 62 | var idle_queue = self.idle_queue; | |
| 63 | self.idle_queue = .{}; | |
| 64 | self.is_running = false; | |
| 65 | held.release(); | |
| 93 | self.is_running = false; | |
| 94 | while (self.idle_queue.popFirst()) |idle_node| | |
| 95 | idle_node.data.set(); | |
| 96 | } | |
| 66 | 97 | |
| 67 | while (idle_queue.popFirst()) |idle_node| | |
| 68 | idle_node.data.set(); | |
| 98 | self.destroyWorkers(self.workers.len); | |
| 99 | self.allocator.free(self.workers); | |
| 69 | 100 | } |
| 70 | 101 | |
| 71 | 102 | pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| 72 | 103 | if (std.builtin.single_threaded) { |
| 73 | @call(.{}, func, args); | |
| 104 | const result = @call(.{}, func, args); | |
| 74 | 105 | return; |
| 75 | 106 | } |
| 107 | ||
| 76 | 108 | const Args = @TypeOf(args); |
| 77 | 109 | const Closure = struct { |
| 78 | 110 | arguments: Args, |
| ... | ... | @@ -83,44 +115,24 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| 83 | 115 | const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable); |
| 84 | 116 | const closure = @fieldParentPtr(@This(), "run_node", run_node); |
| 85 | 117 | const result = @call(.{}, func, closure.arguments); |
| 118 | ||
| 119 | const held = closure.pool.lock.acquire(); | |
| 120 | defer held.release(); | |
| 86 | 121 | closure.pool.allocator.destroy(closure); |
| 87 | 122 | } |
| 88 | 123 | }; |
| 89 | 124 | |
| 125 | const held = self.lock.acquire(); | |
| 126 | defer held.release(); | |
| 127 | ||
| 90 | 128 | const closure = try self.allocator.create(Closure); |
| 91 | 129 | closure.* = .{ |
| 92 | 130 | .arguments = args, |
| 93 | 131 | .pool = self, |
| 94 | 132 | }; |
| 95 | 133 | |
| 96 | const held = self.lock.acquire(); | |
| 97 | 134 | self.run_queue.prepend(&closure.run_node); |
| 98 | 135 | |
| 99 | const idle_node = self.idle_queue.popFirst(); | |
| 100 | held.release(); | |
| 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 | } | |
| 136 | if (self.idle_queue.popFirst()) |idle_node| | |
| 137 | idle_node.data.set(); | |
| 126 | 138 | } |
src/WaitGroup.zig+33-18| ... | ... | @@ -5,11 +5,24 @@ |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const WaitGroup = @This(); |
| 8 | const Event = @import("Event.zig"); | |
| 9 | 8 | |
| 10 | 9 | lock: std.Mutex = .{}, |
| 11 | 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 | 27 | pub fn start(self: *WaitGroup) void { |
| 15 | 28 | const held = self.lock.acquire(); |
| ... | ... | @@ -18,29 +31,31 @@ pub fn start(self: *WaitGroup) void { |
| 18 | 31 | self.counter += 1; |
| 19 | 32 | } |
| 20 | 33 | |
| 21 | pub fn stop(self: *WaitGroup) void { | |
| 22 | var event: ?*Event = null; | |
| 23 | defer if (event) |waiter| | |
| 24 | waiter.set(); | |
| 25 | ||
| 34 | pub fn finish(self: *WaitGroup) void { | |
| 26 | 35 | const held = self.lock.acquire(); |
| 27 | 36 | defer held.release(); |
| 28 | 37 | |
| 29 | 38 | self.counter -= 1; |
| 30 | if (self.counter == 0) | |
| 31 | std.mem.swap(?*Event, &self.event, &event); | |
| 39 | ||
| 40 | if (self.counter == 0) { | |
| 41 | self.event.set(); | |
| 42 | } | |
| 32 | 43 | } |
| 33 | 44 | |
| 34 | 45 | pub fn wait(self: *WaitGroup) void { |
| 35 | var event = Event{}; | |
| 36 | var has_event = false; | |
| 37 | defer if (has_event) | |
| 38 | event.wait(); | |
| 46 | while (true) { | |
| 47 | const held = self.lock.acquire(); | |
| 39 | 48 | |
| 40 | const held = self.lock.acquire(); | |
| 41 | defer held.release(); | |
| 49 | if (self.counter == 0) { | |
| 50 | held.release(); | |
| 51 | return; | |
| 52 | } | |
| 53 | ||
| 54 | held.release(); | |
| 55 | self.event.wait(); | |
| 56 | } | |
| 57 | } | |
| 42 | 58 | |
| 43 | has_event = self.counter != 0; | |
| 44 | if (has_event) | |
| 45 | self.event = &event; | |
| 59 | pub fn reset(self: *WaitGroup) void { | |
| 60 | self.event.reset(); | |
| 46 | 61 | } |