| author | |
| committer | |
| log | 35686262f503372bb9f38a71b891fd19b30adecd |
| tree | 5e9d3af75fc0c3d89ee001e2c922946fabb7d6ac |
| parent | 2faf8c53d226cec1445156cafdec59af5e909fb2 |
| parent | 19f893c6bbb3f5ab4e8d943b0ac2fefa94e5d666 |
| signature |
organize std lib concurrency primitives and add RwLock40 files changed, 2458 insertions(+), 1957 deletions(-)
CMakeLists.txt+5-6| ... | ... | @@ -334,7 +334,6 @@ set(ZIG_STAGE2_SOURCES |
| 334 | 334 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/int.zig" |
| 335 | 335 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig" |
| 336 | 336 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig" |
| 337 | "${CMAKE_SOURCE_DIR}/lib/std/auto_reset_event.zig" | |
| 338 | 337 | "${CMAKE_SOURCE_DIR}/lib/std/base64.zig" |
| 339 | 338 | "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig" |
| 340 | 339 | "${CMAKE_SOURCE_DIR}/lib/std/builtin.zig" |
| ... | ... | @@ -409,7 +408,6 @@ set(ZIG_STAGE2_SOURCES |
| 409 | 408 | "${CMAKE_SOURCE_DIR}/lib/std/meta.zig" |
| 410 | 409 | "${CMAKE_SOURCE_DIR}/lib/std/meta/trailer_flags.zig" |
| 411 | 410 | "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig" |
| 412 | "${CMAKE_SOURCE_DIR}/lib/std/mutex.zig" | |
| 413 | 411 | "${CMAKE_SOURCE_DIR}/lib/std/os.zig" |
| 414 | 412 | "${CMAKE_SOURCE_DIR}/lib/std/os/bits.zig" |
| 415 | 413 | "${CMAKE_SOURCE_DIR}/lib/std/os/bits/linux.zig" |
| ... | ... | @@ -426,8 +424,6 @@ set(ZIG_STAGE2_SOURCES |
| 426 | 424 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" |
| 427 | 425 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" |
| 428 | 426 | "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig" |
| 429 | "${CMAKE_SOURCE_DIR}/lib/std/ResetEvent.zig" | |
| 430 | "${CMAKE_SOURCE_DIR}/lib/std/StaticResetEvent.zig" | |
| 431 | 427 | "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig" |
| 432 | 428 | "${CMAKE_SOURCE_DIR}/lib/std/process.zig" |
| 433 | 429 | "${CMAKE_SOURCE_DIR}/lib/std/rand.zig" |
| ... | ... | @@ -494,7 +490,6 @@ set(ZIG_STAGE2_SOURCES |
| 494 | 490 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivmodti4.zig" |
| 495 | 491 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivti3.zig" |
| 496 | 492 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/umodti3.zig" |
| 497 | "${CMAKE_SOURCE_DIR}/lib/std/SpinLock.zig" | |
| 498 | 493 | "${CMAKE_SOURCE_DIR}/lib/std/start.zig" |
| 499 | 494 | "${CMAKE_SOURCE_DIR}/lib/std/std.zig" |
| 500 | 495 | "${CMAKE_SOURCE_DIR}/lib/std/target.zig" |
| ... | ... | @@ -513,7 +508,11 @@ set(ZIG_STAGE2_SOURCES |
| 513 | 508 | "${CMAKE_SOURCE_DIR}/lib/std/target/systemz.zig" |
| 514 | 509 | "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig" |
| 515 | 510 | "${CMAKE_SOURCE_DIR}/lib/std/target/x86.zig" |
| 516 | "${CMAKE_SOURCE_DIR}/lib/std/thread.zig" | |
| 511 | "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig" | |
| 512 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/AutoResetEvent.zig" | |
| 513 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig" | |
| 514 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig" | |
| 515 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/StaticResetEvent.zig" | |
| 517 | 516 | "${CMAKE_SOURCE_DIR}/lib/std/time.zig" |
| 518 | 517 | "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig" |
| 519 | 518 | "${CMAKE_SOURCE_DIR}/lib/std/zig.zig" |
lib/std/Progress.zig+1-1| ... | ... | @@ -50,7 +50,7 @@ done: bool = true, |
| 50 | 50 | /// Protects the `refresh` function, as well as `node.recently_updated_child`. |
| 51 | 51 | /// Without this, callsites would call `Node.end` and then free `Node` memory |
| 52 | 52 | /// while it was still being accessed by the `refresh` function. |
| 53 | update_lock: std.Mutex = .{}, | |
| 53 | update_lock: std.Thread.Mutex = .{}, | |
| 54 | 54 | |
| 55 | 55 | /// Keeps track of how many columns in the terminal have been output, so that |
| 56 | 56 | /// we can move the cursor back later. |
lib/std/ResetEvent.zig deleted-297| ... | ... | @@ -1,297 +0,0 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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/SpinLock.zig deleted-86| ... | ... | @@ -1,86 +0,0 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 | //! A mutually exclusive lock that grinds the CPU rather than interacting with | |
| 7 | //! the operating system. It does however yield to the OS scheduler while | |
| 8 | //! spinning, when targeting an OS that supports it. | |
| 9 | //! This struct can be initialized directly and statically initialized. The | |
| 10 | //! default state is unlocked. | |
| 11 | ||
| 12 | state: State = State.Unlocked, | |
| 13 | ||
| 14 | const std = @import("std.zig"); | |
| 15 | const builtin = @import("builtin"); | |
| 16 | const SpinLock = @This(); | |
| 17 | ||
| 18 | const State = enum(u8) { | |
| 19 | Unlocked, | |
| 20 | Locked, | |
| 21 | }; | |
| 22 | ||
| 23 | pub const Held = struct { | |
| 24 | spinlock: *SpinLock, | |
| 25 | ||
| 26 | pub fn release(self: Held) void { | |
| 27 | @atomicStore(State, &self.spinlock.state, .Unlocked, .Release); | |
| 28 | } | |
| 29 | }; | |
| 30 | ||
| 31 | pub fn tryAcquire(self: *SpinLock) ?Held { | |
| 32 | return switch (@atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire)) { | |
| 33 | .Unlocked => Held{ .spinlock = self }, | |
| 34 | .Locked => null, | |
| 35 | }; | |
| 36 | } | |
| 37 | ||
| 38 | pub fn acquire(self: *SpinLock) Held { | |
| 39 | while (true) { | |
| 40 | return self.tryAcquire() orelse { | |
| 41 | yield(); | |
| 42 | continue; | |
| 43 | }; | |
| 44 | } | |
| 45 | } | |
| 46 | ||
| 47 | pub fn yield() void { | |
| 48 | // On native windows, SwitchToThread is too expensive, | |
| 49 | // and yielding for 380-410 iterations was found to be | |
| 50 | // a nice sweet spot. Posix systems on the other hand, | |
| 51 | // especially linux, perform better by yielding the thread. | |
| 52 | switch (builtin.os.tag) { | |
| 53 | .windows => loopHint(400), | |
| 54 | else => std.os.sched_yield() catch loopHint(1), | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | /// Hint to the cpu that execution is spinning | |
| 59 | /// for the given amount of iterations. | |
| 60 | pub fn loopHint(iterations: usize) void { | |
| 61 | var i = iterations; | |
| 62 | while (i != 0) : (i -= 1) { | |
| 63 | switch (builtin.arch) { | |
| 64 | // these instructions use a memory clobber as they | |
| 65 | // flush the pipeline of any speculated reads/writes. | |
| 66 | .i386, .x86_64 => asm volatile ("pause" | |
| 67 | : | |
| 68 | : | |
| 69 | : "memory" | |
| 70 | ), | |
| 71 | .arm, .aarch64 => asm volatile ("yield" | |
| 72 | : | |
| 73 | : | |
| 74 | : "memory" | |
| 75 | ), | |
| 76 | else => std.os.sched_yield() catch {}, | |
| 77 | } | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | test "basic usage" { | |
| 82 | var lock: SpinLock = .{}; | |
| 83 | ||
| 84 | const held = lock.acquire(); | |
| 85 | defer held.release(); | |
| 86 | } |
lib/std/StaticResetEvent.zig deleted-396| ... | ... | @@ -1,396 +0,0 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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/Thread.zig created+560| ... | ... | @@ -0,0 +1,560 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 | //! This struct represents a kernel thread, and acts as a namespace for concurrency | |
| 8 | //! primitives that operate on kernel threads. For concurrency primitives that support | |
| 9 | //! both evented I/O and async I/O, see the respective names in the top level std namespace. | |
| 10 | ||
| 11 | data: Data, | |
| 12 | ||
| 13 | pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig"); | |
| 14 | pub const ResetEvent = @import("Thread/ResetEvent.zig"); | |
| 15 | pub const StaticResetEvent = @import("Thread/StaticResetEvent.zig"); | |
| 16 | pub const Mutex = @import("Thread/Mutex.zig"); | |
| 17 | pub const Semaphore = @import("Thread/Semaphore.zig"); | |
| 18 | pub const Condition = @import("Thread/Condition.zig"); | |
| 19 | ||
| 20 | pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc; | |
| 21 | ||
| 22 | const Thread = @This(); | |
| 23 | const std = @import("std.zig"); | |
| 24 | const builtin = std.builtin; | |
| 25 | const os = std.os; | |
| 26 | const mem = std.mem; | |
| 27 | const windows = std.os.windows; | |
| 28 | const c = std.c; | |
| 29 | const assert = std.debug.assert; | |
| 30 | ||
| 31 | const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"; | |
| 32 | ||
| 33 | /// Represents a kernel thread handle. | |
| 34 | /// May be an integer or a pointer depending on the platform. | |
| 35 | /// On Linux and POSIX, this is the same as Id. | |
| 36 | pub const Handle = if (use_pthreads) | |
| 37 | c.pthread_t | |
| 38 | else switch (std.Target.current.os.tag) { | |
| 39 | .linux => i32, | |
| 40 | .windows => windows.HANDLE, | |
| 41 | else => void, | |
| 42 | }; | |
| 43 | ||
| 44 | /// Represents a unique ID per thread. | |
| 45 | /// May be an integer or pointer depending on the platform. | |
| 46 | /// On Linux and POSIX, this is the same as Handle. | |
| 47 | pub const Id = switch (std.Target.current.os.tag) { | |
| 48 | .windows => windows.DWORD, | |
| 49 | else => Handle, | |
| 50 | }; | |
| 51 | ||
| 52 | pub const Data = if (use_pthreads) | |
| 53 | struct { | |
| 54 | handle: Thread.Handle, | |
| 55 | memory: []u8, | |
| 56 | } | |
| 57 | else switch (std.Target.current.os.tag) { | |
| 58 | .linux => struct { | |
| 59 | handle: Thread.Handle, | |
| 60 | memory: []align(mem.page_size) u8, | |
| 61 | }, | |
| 62 | .windows => struct { | |
| 63 | handle: Thread.Handle, | |
| 64 | alloc_start: *c_void, | |
| 65 | heap_handle: windows.HANDLE, | |
| 66 | }, | |
| 67 | else => struct {}, | |
| 68 | }; | |
| 69 | ||
| 70 | /// Signals the processor that it is inside a busy-wait spin-loop ("spin lock"). | |
| 71 | pub fn spinLoopHint() void { | |
| 72 | switch (std.Target.current.cpu.arch) { | |
| 73 | .i386, .x86_64 => asm volatile ("pause" | |
| 74 | : | |
| 75 | : | |
| 76 | : "memory" | |
| 77 | ), | |
| 78 | .arm, .aarch64 => asm volatile ("yield" | |
| 79 | : | |
| 80 | : | |
| 81 | : "memory" | |
| 82 | ), | |
| 83 | else => {}, | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | /// Returns the ID of the calling thread. | |
| 88 | /// Makes a syscall every time the function is called. | |
| 89 | /// On Linux and POSIX, this Id is the same as a Handle. | |
| 90 | pub fn getCurrentId() Id { | |
| 91 | if (use_pthreads) { | |
| 92 | return c.pthread_self(); | |
| 93 | } else | |
| 94 | return switch (std.Target.current.os.tag) { | |
| 95 | .linux => os.linux.gettid(), | |
| 96 | .windows => windows.kernel32.GetCurrentThreadId(), | |
| 97 | else => @compileError("Unsupported OS"), | |
| 98 | }; | |
| 99 | } | |
| 100 | ||
| 101 | /// Returns the handle of this thread. | |
| 102 | /// On Linux and POSIX, this is the same as Id. | |
| 103 | /// On Linux, it is possible that the thread spawned with `spawn` | |
| 104 | /// finishes executing entirely before the clone syscall completes. In this | |
| 105 | /// case, this function will return 0 rather than the no-longer-existing thread's | |
| 106 | /// pid. | |
| 107 | pub fn handle(self: Thread) Handle { | |
| 108 | return self.data.handle; | |
| 109 | } | |
| 110 | ||
| 111 | pub fn wait(self: *Thread) void { | |
| 112 | if (use_pthreads) { | |
| 113 | const err = c.pthread_join(self.data.handle, null); | |
| 114 | switch (err) { | |
| 115 | 0 => {}, | |
| 116 | os.EINVAL => unreachable, | |
| 117 | os.ESRCH => unreachable, | |
| 118 | os.EDEADLK => unreachable, | |
| 119 | else => unreachable, | |
| 120 | } | |
| 121 | std.heap.c_allocator.free(self.data.memory); | |
| 122 | std.heap.c_allocator.destroy(self); | |
| 123 | } else switch (std.Target.current.os.tag) { | |
| 124 | .linux => { | |
| 125 | while (true) { | |
| 126 | const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); | |
| 127 | if (pid_value == 0) break; | |
| 128 | const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null); | |
| 129 | switch (os.linux.getErrno(rc)) { | |
| 130 | 0 => continue, | |
| 131 | os.EINTR => continue, | |
| 132 | os.EAGAIN => continue, | |
| 133 | else => unreachable, | |
| 134 | } | |
| 135 | } | |
| 136 | os.munmap(self.data.memory); | |
| 137 | }, | |
| 138 | .windows => { | |
| 139 | windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable; | |
| 140 | windows.CloseHandle(self.data.handle); | |
| 141 | windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start); | |
| 142 | }, | |
| 143 | else => @compileError("Unsupported OS"), | |
| 144 | } | |
| 145 | } | |
| 146 | ||
| 147 | pub const SpawnError = error{ | |
| 148 | /// A system-imposed limit on the number of threads was encountered. | |
| 149 | /// There are a number of limits that may trigger this error: | |
| 150 | /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), | |
| 151 | /// which limits the number of processes and threads for a real | |
| 152 | /// user ID, was reached; | |
| 153 | /// * the kernel's system-wide limit on the number of processes and | |
| 154 | /// threads, /proc/sys/kernel/threads-max, was reached (see | |
| 155 | /// proc(5)); | |
| 156 | /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was | |
| 157 | /// reached (see proc(5)); or | |
| 158 | /// * the PID limit (pids.max) imposed by the cgroup "process num‐ | |
| 159 | /// ber" (PIDs) controller was reached. | |
| 160 | ThreadQuotaExceeded, | |
| 161 | ||
| 162 | /// The kernel cannot allocate sufficient memory to allocate a task structure | |
| 163 | /// for the child, or to copy those parts of the caller's context that need to | |
| 164 | /// be copied. | |
| 165 | SystemResources, | |
| 166 | ||
| 167 | /// Not enough userland memory to spawn the thread. | |
| 168 | OutOfMemory, | |
| 169 | ||
| 170 | /// `mlockall` is enabled, and the memory needed to spawn the thread | |
| 171 | /// would exceed the limit. | |
| 172 | LockedMemoryLimitExceeded, | |
| 173 | ||
| 174 | Unexpected, | |
| 175 | }; | |
| 176 | ||
| 177 | /// caller must call wait on the returned thread | |
| 178 | /// fn startFn(@TypeOf(context)) T | |
| 179 | /// where T is u8, noreturn, void, or !void | |
| 180 | /// caller must call wait on the returned thread | |
| 181 | pub fn spawn(context: anytype, comptime startFn: anytype) SpawnError!*Thread { | |
| 182 | if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); | |
| 183 | // TODO compile-time call graph analysis to determine stack upper bound | |
| 184 | // https://github.com/ziglang/zig/issues/157 | |
| 185 | const default_stack_size = 16 * 1024 * 1024; | |
| 186 | ||
| 187 | const Context = @TypeOf(context); | |
| 188 | comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context); | |
| 189 | ||
| 190 | if (std.Target.current.os.tag == .windows) { | |
| 191 | const WinThread = struct { | |
| 192 | const OuterContext = struct { | |
| 193 | thread: Thread, | |
| 194 | inner: Context, | |
| 195 | }; | |
| 196 | fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD { | |
| 197 | const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; | |
| 198 | ||
| 199 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | |
| 200 | .NoReturn => { | |
| 201 | startFn(arg); | |
| 202 | }, | |
| 203 | .Void => { | |
| 204 | startFn(arg); | |
| 205 | return 0; | |
| 206 | }, | |
| 207 | .Int => |info| { | |
| 208 | if (info.bits != 8) { | |
| 209 | @compileError(bad_startfn_ret); | |
| 210 | } | |
| 211 | return startFn(arg); | |
| 212 | }, | |
| 213 | .ErrorUnion => |info| { | |
| 214 | if (info.payload != void) { | |
| 215 | @compileError(bad_startfn_ret); | |
| 216 | } | |
| 217 | startFn(arg) catch |err| { | |
| 218 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | |
| 219 | if (@errorReturnTrace()) |trace| { | |
| 220 | std.debug.dumpStackTrace(trace.*); | |
| 221 | } | |
| 222 | }; | |
| 223 | return 0; | |
| 224 | }, | |
| 225 | else => @compileError(bad_startfn_ret), | |
| 226 | } | |
| 227 | } | |
| 228 | }; | |
| 229 | ||
| 230 | const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory; | |
| 231 | const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); | |
| 232 | const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory; | |
| 233 | errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0); | |
| 234 | const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; | |
| 235 | const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; | |
| 236 | outer_context.* = WinThread.OuterContext{ | |
| 237 | .thread = Thread{ | |
| 238 | .data = Thread.Data{ | |
| 239 | .heap_handle = heap_handle, | |
| 240 | .alloc_start = bytes_ptr, | |
| 241 | .handle = undefined, | |
| 242 | }, | |
| 243 | }, | |
| 244 | .inner = context, | |
| 245 | }; | |
| 246 | ||
| 247 | const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); | |
| 248 | outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { | |
| 249 | switch (windows.kernel32.GetLastError()) { | |
| 250 | else => |err| return windows.unexpectedError(err), | |
| 251 | } | |
| 252 | }; | |
| 253 | return &outer_context.thread; | |
| 254 | } | |
| 255 | ||
| 256 | const MainFuncs = struct { | |
| 257 | fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 { | |
| 258 | const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; | |
| 259 | ||
| 260 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | |
| 261 | .NoReturn => { | |
| 262 | startFn(arg); | |
| 263 | }, | |
| 264 | .Void => { | |
| 265 | startFn(arg); | |
| 266 | return 0; | |
| 267 | }, | |
| 268 | .Int => |info| { | |
| 269 | if (info.bits != 8) { | |
| 270 | @compileError(bad_startfn_ret); | |
| 271 | } | |
| 272 | return startFn(arg); | |
| 273 | }, | |
| 274 | .ErrorUnion => |info| { | |
| 275 | if (info.payload != void) { | |
| 276 | @compileError(bad_startfn_ret); | |
| 277 | } | |
| 278 | startFn(arg) catch |err| { | |
| 279 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | |
| 280 | if (@errorReturnTrace()) |trace| { | |
| 281 | std.debug.dumpStackTrace(trace.*); | |
| 282 | } | |
| 283 | }; | |
| 284 | return 0; | |
| 285 | }, | |
| 286 | else => @compileError(bad_startfn_ret), | |
| 287 | } | |
| 288 | } | |
| 289 | fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void { | |
| 290 | const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; | |
| 291 | ||
| 292 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | |
| 293 | .NoReturn => { | |
| 294 | startFn(arg); | |
| 295 | }, | |
| 296 | .Void => { | |
| 297 | startFn(arg); | |
| 298 | return null; | |
| 299 | }, | |
| 300 | .Int => |info| { | |
| 301 | if (info.bits != 8) { | |
| 302 | @compileError(bad_startfn_ret); | |
| 303 | } | |
| 304 | // pthreads don't support exit status, ignore value | |
| 305 | _ = startFn(arg); | |
| 306 | return null; | |
| 307 | }, | |
| 308 | .ErrorUnion => |info| { | |
| 309 | if (info.payload != void) { | |
| 310 | @compileError(bad_startfn_ret); | |
| 311 | } | |
| 312 | startFn(arg) catch |err| { | |
| 313 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | |
| 314 | if (@errorReturnTrace()) |trace| { | |
| 315 | std.debug.dumpStackTrace(trace.*); | |
| 316 | } | |
| 317 | }; | |
| 318 | return null; | |
| 319 | }, | |
| 320 | else => @compileError(bad_startfn_ret), | |
| 321 | } | |
| 322 | } | |
| 323 | }; | |
| 324 | ||
| 325 | if (Thread.use_pthreads) { | |
| 326 | var attr: c.pthread_attr_t = undefined; | |
| 327 | if (c.pthread_attr_init(&attr) != 0) return error.SystemResources; | |
| 328 | defer assert(c.pthread_attr_destroy(&attr) == 0); | |
| 329 | ||
| 330 | const thread_obj = try std.heap.c_allocator.create(Thread); | |
| 331 | errdefer std.heap.c_allocator.destroy(thread_obj); | |
| 332 | if (@sizeOf(Context) > 0) { | |
| 333 | thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced( | |
| 334 | u8, | |
| 335 | @alignOf(Context), | |
| 336 | @sizeOf(Context), | |
| 337 | .at_least, | |
| 338 | ); | |
| 339 | errdefer std.heap.c_allocator.free(thread_obj.data.memory); | |
| 340 | mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context)); | |
| 341 | } else { | |
| 342 | thread_obj.data.memory = @as([*]u8, undefined)[0..0]; | |
| 343 | } | |
| 344 | ||
| 345 | // Use the same set of parameters used by the libc-less impl. | |
| 346 | assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0); | |
| 347 | assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0); | |
| 348 | ||
| 349 | const err = c.pthread_create( | |
| 350 | &thread_obj.data.handle, | |
| 351 | &attr, | |
| 352 | MainFuncs.posixThreadMain, | |
| 353 | thread_obj.data.memory.ptr, | |
| 354 | ); | |
| 355 | switch (err) { | |
| 356 | 0 => return thread_obj, | |
| 357 | os.EAGAIN => return error.SystemResources, | |
| 358 | os.EPERM => unreachable, | |
| 359 | os.EINVAL => unreachable, | |
| 360 | else => return os.unexpectedErrno(@intCast(usize, err)), | |
| 361 | } | |
| 362 | ||
| 363 | return thread_obj; | |
| 364 | } | |
| 365 | ||
| 366 | var guard_end_offset: usize = undefined; | |
| 367 | var stack_end_offset: usize = undefined; | |
| 368 | var thread_start_offset: usize = undefined; | |
| 369 | var context_start_offset: usize = undefined; | |
| 370 | var tls_start_offset: usize = undefined; | |
| 371 | const mmap_len = blk: { | |
| 372 | var l: usize = mem.page_size; | |
| 373 | // Allocate a guard page right after the end of the stack region | |
| 374 | guard_end_offset = l; | |
| 375 | // The stack itself, which grows downwards. | |
| 376 | l = mem.alignForward(l + default_stack_size, mem.page_size); | |
| 377 | stack_end_offset = l; | |
| 378 | // Above the stack, so that it can be in the same mmap call, put the Thread object. | |
| 379 | l = mem.alignForward(l, @alignOf(Thread)); | |
| 380 | thread_start_offset = l; | |
| 381 | l += @sizeOf(Thread); | |
| 382 | // Next, the Context object. | |
| 383 | if (@sizeOf(Context) != 0) { | |
| 384 | l = mem.alignForward(l, @alignOf(Context)); | |
| 385 | context_start_offset = l; | |
| 386 | l += @sizeOf(Context); | |
| 387 | } | |
| 388 | // Finally, the Thread Local Storage, if any. | |
| 389 | l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align); | |
| 390 | tls_start_offset = l; | |
| 391 | l += os.linux.tls.tls_image.alloc_size; | |
| 392 | // Round the size to the page size. | |
| 393 | break :blk mem.alignForward(l, mem.page_size); | |
| 394 | }; | |
| 395 | ||
| 396 | const mmap_slice = mem: { | |
| 397 | // Map the whole stack with no rw permissions to avoid | |
| 398 | // committing the whole region right away | |
| 399 | const mmap_slice = os.mmap( | |
| 400 | null, | |
| 401 | mmap_len, | |
| 402 | os.PROT_NONE, | |
| 403 | os.MAP_PRIVATE | os.MAP_ANONYMOUS, | |
| 404 | -1, | |
| 405 | 0, | |
| 406 | ) catch |err| switch (err) { | |
| 407 | error.MemoryMappingNotSupported => unreachable, | |
| 408 | error.AccessDenied => unreachable, | |
| 409 | error.PermissionDenied => unreachable, | |
| 410 | else => |e| return e, | |
| 411 | }; | |
| 412 | errdefer os.munmap(mmap_slice); | |
| 413 | ||
| 414 | // Map everything but the guard page as rw | |
| 415 | os.mprotect( | |
| 416 | mmap_slice[guard_end_offset..], | |
| 417 | os.PROT_READ | os.PROT_WRITE, | |
| 418 | ) catch |err| switch (err) { | |
| 419 | error.AccessDenied => unreachable, | |
| 420 | else => |e| return e, | |
| 421 | }; | |
| 422 | ||
| 423 | break :mem mmap_slice; | |
| 424 | }; | |
| 425 | ||
| 426 | const mmap_addr = @ptrToInt(mmap_slice.ptr); | |
| 427 | ||
| 428 | const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); | |
| 429 | thread_ptr.data.memory = mmap_slice; | |
| 430 | ||
| 431 | var arg: usize = undefined; | |
| 432 | if (@sizeOf(Context) != 0) { | |
| 433 | arg = mmap_addr + context_start_offset; | |
| 434 | const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); | |
| 435 | context_ptr.* = context; | |
| 436 | } | |
| 437 | ||
| 438 | if (std.Target.current.os.tag == .linux) { | |
| 439 | const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | | |
| 440 | os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM | | |
| 441 | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | | |
| 442 | os.CLONE_DETACHED | os.CLONE_SETTLS; | |
| 443 | // This structure is only needed when targeting i386 | |
| 444 | var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined; | |
| 445 | ||
| 446 | const tls_area = mmap_slice[tls_start_offset..]; | |
| 447 | const tp_value = os.linux.tls.prepareTLS(tls_area); | |
| 448 | ||
| 449 | const newtls = blk: { | |
| 450 | if (std.Target.current.cpu.arch == .i386) { | |
| 451 | user_desc = os.linux.user_desc{ | |
| 452 | .entry_number = os.linux.tls.tls_image.gdt_entry_number, | |
| 453 | .base_addr = tp_value, | |
| 454 | .limit = 0xfffff, | |
| 455 | .seg_32bit = 1, | |
| 456 | .contents = 0, // Data | |
| 457 | .read_exec_only = 0, | |
| 458 | .limit_in_pages = 1, | |
| 459 | .seg_not_present = 0, | |
| 460 | .useable = 1, | |
| 461 | }; | |
| 462 | break :blk @ptrToInt(&user_desc); | |
| 463 | } else { | |
| 464 | break :blk tp_value; | |
| 465 | } | |
| 466 | }; | |
| 467 | ||
| 468 | const rc = os.linux.clone( | |
| 469 | MainFuncs.linuxThreadMain, | |
| 470 | mmap_addr + stack_end_offset, | |
| 471 | flags, | |
| 472 | arg, | |
| 473 | &thread_ptr.data.handle, | |
| 474 | newtls, | |
| 475 | &thread_ptr.data.handle, | |
| 476 | ); | |
| 477 | switch (os.errno(rc)) { | |
| 478 | 0 => return thread_ptr, | |
| 479 | os.EAGAIN => return error.ThreadQuotaExceeded, | |
| 480 | os.EINVAL => unreachable, | |
| 481 | os.ENOMEM => return error.SystemResources, | |
| 482 | os.ENOSPC => unreachable, | |
| 483 | os.EPERM => unreachable, | |
| 484 | os.EUSERS => unreachable, | |
| 485 | else => |err| return os.unexpectedErrno(err), | |
| 486 | } | |
| 487 | } else { | |
| 488 | @compileError("Unsupported OS"); | |
| 489 | } | |
| 490 | } | |
| 491 | ||
| 492 | pub const CpuCountError = error{ | |
| 493 | PermissionDenied, | |
| 494 | SystemResources, | |
| 495 | Unexpected, | |
| 496 | }; | |
| 497 | ||
| 498 | pub fn cpuCount() CpuCountError!usize { | |
| 499 | if (std.Target.current.os.tag == .linux) { | |
| 500 | const cpu_set = try os.sched_getaffinity(0); | |
| 501 | return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast | |
| 502 | } | |
| 503 | if (std.Target.current.os.tag == .windows) { | |
| 504 | return os.windows.peb().NumberOfProcessors; | |
| 505 | } | |
| 506 | if (std.Target.current.os.tag == .openbsd) { | |
| 507 | var count: c_int = undefined; | |
| 508 | var count_size: usize = @sizeOf(c_int); | |
| 509 | const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE }; | |
| 510 | os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { | |
| 511 | error.NameTooLong, error.UnknownName => unreachable, | |
| 512 | else => |e| return e, | |
| 513 | }; | |
| 514 | return @intCast(usize, count); | |
| 515 | } | |
| 516 | var count: c_int = undefined; | |
| 517 | var count_len: usize = @sizeOf(c_int); | |
| 518 | const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; | |
| 519 | os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { | |
| 520 | error.NameTooLong, error.UnknownName => unreachable, | |
| 521 | else => |e| return e, | |
| 522 | }; | |
| 523 | return @intCast(usize, count); | |
| 524 | } | |
| 525 | ||
| 526 | pub fn getCurrentThreadId() u64 { | |
| 527 | switch (std.Target.current.os.tag) { | |
| 528 | .linux => { | |
| 529 | // Use the syscall directly as musl doesn't provide a wrapper. | |
| 530 | return @bitCast(u32, os.linux.gettid()); | |
| 531 | }, | |
| 532 | .windows => { | |
| 533 | return os.windows.kernel32.GetCurrentThreadId(); | |
| 534 | }, | |
| 535 | .macos, .ios, .watchos, .tvos => { | |
| 536 | var thread_id: u64 = undefined; | |
| 537 | // Pass thread=null to get the current thread ID. | |
| 538 | assert(c.pthread_threadid_np(null, &thread_id) == 0); | |
| 539 | return thread_id; | |
| 540 | }, | |
| 541 | .netbsd => { | |
| 542 | return @bitCast(u32, c._lwp_self()); | |
| 543 | }, | |
| 544 | .freebsd => { | |
| 545 | return @bitCast(u32, c.pthread_getthreadid_np()); | |
| 546 | }, | |
| 547 | .openbsd => { | |
| 548 | return @bitCast(u32, c.getthrid()); | |
| 549 | }, | |
| 550 | else => { | |
| 551 | @compileError("getCurrentThreadId not implemented for this platform"); | |
| 552 | }, | |
| 553 | } | |
| 554 | } | |
| 555 | ||
| 556 | test "" { | |
| 557 | if (!builtin.single_threaded) { | |
| 558 | std.testing.refAllDecls(@This()); | |
| 559 | } | |
| 560 | } |
lib/std/Thread/AutoResetEvent.zig created+228| ... | ... | @@ -0,0 +1,228 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 | //! Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`. | |
| 8 | //! Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like). | |
| 9 | //! | |
| 10 | //! AutoResetEvent has 3 possible states: | |
| 11 | //! - UNSET: the AutoResetEvent is currently unset | |
| 12 | //! - SET: the AutoResetEvent was notified before a wait() was called | |
| 13 | //! - <StaticResetEvent pointer>: there is an active waiter waiting for a notification. | |
| 14 | //! | |
| 15 | //! When attempting to wait: | |
| 16 | //! if the event is unset, it registers a ResetEvent pointer to be notified when the event is set | |
| 17 | //! if the event is already set, then it consumes the notification and resets the event. | |
| 18 | //! | |
| 19 | //! When attempting to notify: | |
| 20 | //! if the event is unset, then we set the event | |
| 21 | //! if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent | |
| 22 | //! | |
| 23 | //! This ensures that the event is automatically reset after a wait() has been issued | |
| 24 | //! and avoids the race condition when using StaticResetEvent in the following scenario: | |
| 25 | //! thread 1 | thread 2 | |
| 26 | //! StaticResetEvent.wait() | | |
| 27 | //! | StaticResetEvent.set() | |
| 28 | //! | StaticResetEvent.set() | |
| 29 | //! StaticResetEvent.reset() | | |
| 30 | //! StaticResetEvent.wait() | (missed the second .set() notification above) | |
| 31 | ||
| 32 | state: usize = UNSET, | |
| 33 | ||
| 34 | const std = @import("../std.zig"); | |
| 35 | const builtin = @import("builtin"); | |
| 36 | const testing = std.testing; | |
| 37 | const assert = std.debug.assert; | |
| 38 | const StaticResetEvent = std.Thread.StaticResetEvent; | |
| 39 | const AutoResetEvent = @This(); | |
| 40 | ||
| 41 | const UNSET = 0; | |
| 42 | const SET = 1; | |
| 43 | ||
| 44 | /// the minimum alignment for the `*StaticResetEvent` created by wait*() | |
| 45 | const event_align = std.math.max(@alignOf(StaticResetEvent), 2); | |
| 46 | ||
| 47 | pub fn wait(self: *AutoResetEvent) void { | |
| 48 | self.waitFor(null) catch unreachable; | |
| 49 | } | |
| 50 | ||
| 51 | pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { | |
| 52 | return self.waitFor(timeout); | |
| 53 | } | |
| 54 | ||
| 55 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { | |
| 56 | // lazily initialized StaticResetEvent | |
| 57 | var reset_event: StaticResetEvent align(event_align) = undefined; | |
| 58 | var has_reset_event = false; | |
| 59 | ||
| 60 | var state = @atomicLoad(usize, &self.state, .SeqCst); | |
| 61 | while (true) { | |
| 62 | // consume a notification if there is any | |
| 63 | if (state == SET) { | |
| 64 | @atomicStore(usize, &self.state, UNSET, .SeqCst); | |
| 65 | return; | |
| 66 | } | |
| 67 | ||
| 68 | // check if theres currently a pending ResetEvent pointer already registered | |
| 69 | if (state != UNSET) { | |
| 70 | unreachable; // multiple waiting threads on the same AutoResetEvent | |
| 71 | } | |
| 72 | ||
| 73 | // lazily initialize the ResetEvent if it hasn't been already | |
| 74 | if (!has_reset_event) { | |
| 75 | has_reset_event = true; | |
| 76 | reset_event = .{}; | |
| 77 | } | |
| 78 | ||
| 79 | // Since the AutoResetEvent currently isnt set, | |
| 80 | // try to register our ResetEvent on it to wait | |
| 81 | // for a set() call from another thread. | |
| 82 | if (@cmpxchgWeak( | |
| 83 | usize, | |
| 84 | &self.state, | |
| 85 | UNSET, | |
| 86 | @ptrToInt(&reset_event), | |
| 87 | .SeqCst, | |
| 88 | .SeqCst, | |
| 89 | )) |new_state| { | |
| 90 | state = new_state; | |
| 91 | continue; | |
| 92 | } | |
| 93 | ||
| 94 | // if no timeout was specified, then just wait forever | |
| 95 | const timeout_ns = timeout orelse { | |
| 96 | reset_event.wait(); | |
| 97 | return; | |
| 98 | }; | |
| 99 | ||
| 100 | // wait with a timeout and return if signalled via set() | |
| 101 | switch (reset_event.timedWait(timeout_ns)) { | |
| 102 | .event_set => return, | |
| 103 | .timed_out => {}, | |
| 104 | } | |
| 105 | ||
| 106 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. | |
| 107 | // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. | |
| 108 | state = @cmpxchgStrong( | |
| 109 | usize, | |
| 110 | &self.state, | |
| 111 | @ptrToInt(&reset_event), | |
| 112 | UNSET, | |
| 113 | .SeqCst, | |
| 114 | .SeqCst, | |
| 115 | ) orelse return error.TimedOut; | |
| 116 | ||
| 117 | // We didn't manage to unregister ourselves from the state. | |
| 118 | if (state == SET) { | |
| 119 | unreachable; // AutoResetEvent notified without waking up the waiting thread | |
| 120 | } else if (state != UNSET) { | |
| 121 | unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out | |
| 122 | } | |
| 123 | ||
| 124 | // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up. | |
| 125 | // We need to wait for it to wake up our ResetEvent before we can return and invalidate it. | |
| 126 | // We don't return error.TimedOut here as it technically notified us while we were "timing out". | |
| 127 | reset_event.wait(); | |
| 128 | return; | |
| 129 | } | |
| 130 | } | |
| 131 | ||
| 132 | pub fn set(self: *AutoResetEvent) void { | |
| 133 | var state = @atomicLoad(usize, &self.state, .SeqCst); | |
| 134 | while (true) { | |
| 135 | // If the AutoResetEvent is already set, there is nothing else left to do | |
| 136 | if (state == SET) { | |
| 137 | return; | |
| 138 | } | |
| 139 | ||
| 140 | // If the AutoResetEvent isn't set, | |
| 141 | // then try to leave a notification for the wait() thread that we set() it. | |
| 142 | if (state == UNSET) { | |
| 143 | state = @cmpxchgWeak( | |
| 144 | usize, | |
| 145 | &self.state, | |
| 146 | UNSET, | |
| 147 | SET, | |
| 148 | .SeqCst, | |
| 149 | .SeqCst, | |
| 150 | ) orelse return; | |
| 151 | continue; | |
| 152 | } | |
| 153 | ||
| 154 | // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting. | |
| 155 | // Try to acquire ownership of it so that we can wake it up. | |
| 156 | // This also resets the AutoResetEvent so that there is no race condition as defined above. | |
| 157 | if (@cmpxchgWeak( | |
| 158 | usize, | |
| 159 | &self.state, | |
| 160 | state, | |
| 161 | UNSET, | |
| 162 | .SeqCst, | |
| 163 | .SeqCst, | |
| 164 | )) |new_state| { | |
| 165 | state = new_state; | |
| 166 | continue; | |
| 167 | } | |
| 168 | ||
| 169 | const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state); | |
| 170 | reset_event.set(); | |
| 171 | return; | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | test "basic usage" { | |
| 176 | // test local code paths | |
| 177 | { | |
| 178 | var event = AutoResetEvent{}; | |
| 179 | testing.expectError(error.TimedOut, event.timedWait(1)); | |
| 180 | event.set(); | |
| 181 | event.wait(); | |
| 182 | } | |
| 183 | ||
| 184 | // test cross-thread signaling | |
| 185 | if (builtin.single_threaded) | |
| 186 | return; | |
| 187 | ||
| 188 | const Context = struct { | |
| 189 | value: u128 = 0, | |
| 190 | in: AutoResetEvent = AutoResetEvent{}, | |
| 191 | out: AutoResetEvent = AutoResetEvent{}, | |
| 192 | ||
| 193 | const Self = @This(); | |
| 194 | ||
| 195 | fn sender(self: *Self) void { | |
| 196 | testing.expect(self.value == 0); | |
| 197 | self.value = 1; | |
| 198 | self.out.set(); | |
| 199 | ||
| 200 | self.in.wait(); | |
| 201 | testing.expect(self.value == 2); | |
| 202 | self.value = 3; | |
| 203 | self.out.set(); | |
| 204 | ||
| 205 | self.in.wait(); | |
| 206 | testing.expect(self.value == 4); | |
| 207 | } | |
| 208 | ||
| 209 | fn receiver(self: *Self) void { | |
| 210 | self.out.wait(); | |
| 211 | testing.expect(self.value == 1); | |
| 212 | self.value = 2; | |
| 213 | self.in.set(); | |
| 214 | ||
| 215 | self.out.wait(); | |
| 216 | testing.expect(self.value == 3); | |
| 217 | self.value = 4; | |
| 218 | self.in.set(); | |
| 219 | } | |
| 220 | }; | |
| 221 | ||
| 222 | var context = Context{}; | |
| 223 | const send_thread = try std.Thread.spawn(&context, Context.sender); | |
| 224 | const recv_thread = try std.Thread.spawn(&context, Context.receiver); | |
| 225 | ||
| 226 | send_thread.wait(); | |
| 227 | recv_thread.wait(); | |
| 228 | } |
lib/std/Thread/Condition.zig created+182| ... | ... | @@ -0,0 +1,182 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 condition provides a way for a kernel thread to block until it is signaled | |
| 8 | //! to wake up. Spurious wakeups are possible. | |
| 9 | //! This API supports static initialization and does not require deinitialization. | |
| 10 | ||
| 11 | impl: Impl, | |
| 12 | ||
| 13 | const std = @import("../std.zig"); | |
| 14 | const Condition = @This(); | |
| 15 | const windows = std.os.windows; | |
| 16 | const linux = std.os.linux; | |
| 17 | const Mutex = std.Thread.Mutex; | |
| 18 | const assert = std.debug.assert; | |
| 19 | ||
| 20 | const Impl = if (std.builtin.single_threaded) | |
| 21 | SingleThreadedCondition | |
| 22 | else if (std.Target.current.os.tag == .windows) | |
| 23 | WindowsCondition | |
| 24 | else if (std.Thread.use_pthreads) | |
| 25 | PthreadCondition | |
| 26 | else | |
| 27 | AtomicCondition; | |
| 28 | ||
| 29 | pub const SingleThreadedCondition = struct { | |
| 30 | pub fn wait(cond: *SingleThreadedCondition, mutex: *Mutex) void { | |
| 31 | unreachable; // deadlock detected | |
| 32 | } | |
| 33 | ||
| 34 | pub fn signal(cond: *SingleThreadedCondition) void {} | |
| 35 | ||
| 36 | pub fn broadcast(cond: *SingleThreadedCondition) void {} | |
| 37 | }; | |
| 38 | ||
| 39 | pub const WindowsCondition = struct { | |
| 40 | cond: windows.CONDITION_VARIABLE = windows.CONDITION_VARIABLE_INIT, | |
| 41 | ||
| 42 | pub fn wait(cond: *WindowsCondition, mutex: *Mutex) void { | |
| 43 | const rc = windows.kernel32.SleepConditionVariableSRW( | |
| 44 | &cond.cond, | |
| 45 | &mutex.srwlock, | |
| 46 | windows.INFINITE, | |
| 47 | @as(windows.ULONG, 0), | |
| 48 | ); | |
| 49 | assert(rc != windows.FALSE); | |
| 50 | } | |
| 51 | ||
| 52 | pub fn signal(cond: *WindowsCondition) void { | |
| 53 | windows.kernel32.WakeConditionVariable(&cond.cond); | |
| 54 | } | |
| 55 | ||
| 56 | pub fn broadcast(cond: *WindowsCondition) void { | |
| 57 | windows.kernel32.WakeAllConditionVariable(&cond.cond); | |
| 58 | } | |
| 59 | }; | |
| 60 | ||
| 61 | pub const PthreadCondition = struct { | |
| 62 | cond: std.c.pthread_cond_t = .{}, | |
| 63 | ||
| 64 | pub fn wait(cond: *PthreadCondition, mutex: *Mutex) void { | |
| 65 | const rc = std.c.pthread_cond_wait(&cond.cond, &mutex.mutex); | |
| 66 | assert(rc == 0); | |
| 67 | } | |
| 68 | ||
| 69 | pub fn signal(cond: *PthreadCondition) void { | |
| 70 | const rc = std.c.pthread_cond_signal(&cond.cond); | |
| 71 | assert(rc == 0); | |
| 72 | } | |
| 73 | ||
| 74 | pub fn broadcast(cond: *PthreadCondition) void { | |
| 75 | const rc = std.c.pthread_cond_broadcast(&cond.cond); | |
| 76 | assert(rc == 0); | |
| 77 | } | |
| 78 | }; | |
| 79 | ||
| 80 | pub const AtomicCondition = struct { | |
| 81 | pending: bool = false, | |
| 82 | queue_mutex: Mutex = .{}, | |
| 83 | queue_list: QueueList = .{}, | |
| 84 | ||
| 85 | pub const QueueList = std.SinglyLinkedList(QueueItem); | |
| 86 | ||
| 87 | pub const QueueItem = struct { | |
| 88 | futex: i32 = 0, | |
| 89 | ||
| 90 | fn wait(cond: *@This()) void { | |
| 91 | while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { | |
| 92 | switch (std.Target.current.os.tag) { | |
| 93 | .linux => { | |
| 94 | switch (linux.getErrno(linux.futex_wait( | |
| 95 | &cond.futex, | |
| 96 | linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT, | |
| 97 | 0, | |
| 98 | null, | |
| 99 | ))) { | |
| 100 | 0 => {}, | |
| 101 | std.os.EINTR => {}, | |
| 102 | std.os.EAGAIN => {}, | |
| 103 | else => unreachable, | |
| 104 | } | |
| 105 | }, | |
| 106 | else => spinLoopHint(), | |
| 107 | } | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | fn notify(cond: *@This()) void { | |
| 112 | @atomicStore(i32, &cond.futex, 1, .Release); | |
| 113 | ||
| 114 | switch (std.Target.current.os.tag) { | |
| 115 | .linux => { | |
| 116 | switch (linux.getErrno(linux.futex_wake( | |
| 117 | &cond.futex, | |
| 118 | linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE, | |
| 119 | 1, | |
| 120 | ))) { | |
| 121 | 0 => {}, | |
| 122 | std.os.EFAULT => {}, | |
| 123 | else => unreachable, | |
| 124 | } | |
| 125 | }, | |
| 126 | else => {}, | |
| 127 | } | |
| 128 | } | |
| 129 | }; | |
| 130 | ||
| 131 | pub fn wait(cond: *AtomicCondition, mutex: *Mutex) void { | |
| 132 | var waiter = QueueList.Node{ .data = .{} }; | |
| 133 | ||
| 134 | { | |
| 135 | const held = cond.queue_mutex.acquire(); | |
| 136 | defer held.release(); | |
| 137 | ||
| 138 | cond.queue_list.prepend(&waiter); | |
| 139 | @atomicStore(bool, &cond.pending, true, .SeqCst); | |
| 140 | } | |
| 141 | ||
| 142 | mutex.unlock(); | |
| 143 | waiter.data.wait(); | |
| 144 | mutex.lock(); | |
| 145 | } | |
| 146 | ||
| 147 | pub fn signal(cond: *AtomicCondition) void { | |
| 148 | if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) | |
| 149 | return; | |
| 150 | ||
| 151 | const maybe_waiter = blk: { | |
| 152 | const held = cond.queue_mutex.acquire(); | |
| 153 | defer held.release(); | |
| 154 | ||
| 155 | const maybe_waiter = cond.queue_list.popFirst(); | |
| 156 | @atomicStore(bool, &cond.pending, cond.queue_list.first != null, .SeqCst); | |
| 157 | break :blk maybe_waiter; | |
| 158 | }; | |
| 159 | ||
| 160 | if (maybe_waiter) |waiter| | |
| 161 | waiter.data.notify(); | |
| 162 | } | |
| 163 | ||
| 164 | pub fn broadcast(cond: *AtomicCondition) void { | |
| 165 | if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) | |
| 166 | return; | |
| 167 | ||
| 168 | @atomicStore(bool, &cond.pending, false, .SeqCst); | |
| 169 | ||
| 170 | var waiters = blk: { | |
| 171 | const held = cond.queue_mutex.acquire(); | |
| 172 | defer held.release(); | |
| 173 | ||
| 174 | const waiters = cond.queue_list; | |
| 175 | cond.queue_list = .{}; | |
| 176 | break :blk waiters; | |
| 177 | }; | |
| 178 | ||
| 179 | while (waiters.popFirst()) |waiter| | |
| 180 | waiter.data.notify(); | |
| 181 | } | |
| 182 | }; |
lib/std/Thread/Mutex.zig created+319| ... | ... | @@ -0,0 +1,319 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 | //! Lock may be held only once. If the same thread tries to acquire | |
| 8 | //! the same mutex twice, it deadlocks. This type supports static | |
| 9 | //! initialization and is at most `@sizeOf(usize)` in size. When an | |
| 10 | //! application is built in single threaded release mode, all the | |
| 11 | //! functions are no-ops. In single threaded debug mode, there is | |
| 12 | //! deadlock detection. | |
| 13 | //! | |
| 14 | //! Example usage: | |
| 15 | //! var m = Mutex{}; | |
| 16 | //! | |
| 17 | //! const lock = m.acquire(); | |
| 18 | //! defer lock.release(); | |
| 19 | //! ... critical code | |
| 20 | //! | |
| 21 | //! Non-blocking: | |
| 22 | //! if (m.tryAcquire) |lock| { | |
| 23 | //! defer lock.release(); | |
| 24 | //! // ... critical section | |
| 25 | //! } else { | |
| 26 | //! // ... lock not acquired | |
| 27 | //! } | |
| 28 | ||
| 29 | impl: Impl = .{}, | |
| 30 | ||
| 31 | const Mutex = @This(); | |
| 32 | const std = @import("../std.zig"); | |
| 33 | const builtin = std.builtin; | |
| 34 | const os = std.os; | |
| 35 | const assert = std.debug.assert; | |
| 36 | const windows = os.windows; | |
| 37 | const linux = os.linux; | |
| 38 | const testing = std.testing; | |
| 39 | const StaticResetEvent = std.thread.StaticResetEvent; | |
| 40 | ||
| 41 | /// Try to acquire the mutex without blocking. Returns `null` if the mutex is | |
| 42 | /// unavailable. Otherwise returns `Held`. Call `release` on `Held`. | |
| 43 | pub fn tryAcquire(m: *Mutex) ?Impl.Held { | |
| 44 | return m.impl.tryAcquire(); | |
| 45 | } | |
| 46 | ||
| 47 | /// Acquire the mutex. Deadlocks if the mutex is already | |
| 48 | /// held by the calling thread. | |
| 49 | pub fn acquire(m: *Mutex) Impl.Held { | |
| 50 | return m.impl.acquire(); | |
| 51 | } | |
| 52 | ||
| 53 | const Impl = if (builtin.single_threaded) | |
| 54 | Dummy | |
| 55 | else if (builtin.os.tag == .windows) | |
| 56 | WindowsMutex | |
| 57 | else if (std.Thread.use_pthreads) | |
| 58 | PthreadMutex | |
| 59 | else | |
| 60 | AtomicMutex; | |
| 61 | ||
| 62 | pub const AtomicMutex = struct { | |
| 63 | state: State = .unlocked, | |
| 64 | ||
| 65 | const State = enum(i32) { | |
| 66 | unlocked, | |
| 67 | locked, | |
| 68 | waiting, | |
| 69 | }; | |
| 70 | ||
| 71 | pub const Held = struct { | |
| 72 | mutex: *AtomicMutex, | |
| 73 | ||
| 74 | pub fn release(held: Held) void { | |
| 75 | switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) { | |
| 76 | .unlocked => unreachable, | |
| 77 | .locked => {}, | |
| 78 | .waiting => held.mutex.unlockSlow(), | |
| 79 | } | |
| 80 | } | |
| 81 | }; | |
| 82 | ||
| 83 | pub fn tryAcquire(m: *AtomicMutex) ?Held { | |
| 84 | if (@cmpxchgStrong( | |
| 85 | State, | |
| 86 | &m.state, | |
| 87 | .unlocked, | |
| 88 | .locked, | |
| 89 | .Acquire, | |
| 90 | .Monotonic, | |
| 91 | ) == null) { | |
| 92 | return Held{ .mutex = m }; | |
| 93 | } else { | |
| 94 | return null; | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | pub fn acquire(m: *AtomicMutex) Held { | |
| 99 | switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) { | |
| 100 | .unlocked => {}, | |
| 101 | else => |s| m.lockSlow(s), | |
| 102 | } | |
| 103 | return Held{ .mutex = m }; | |
| 104 | } | |
| 105 | ||
| 106 | fn lockSlow(m: *AtomicMutex, current_state: State) void { | |
| 107 | @setCold(true); | |
| 108 | var new_state = current_state; | |
| 109 | ||
| 110 | var spin: u8 = 0; | |
| 111 | while (spin < 100) : (spin += 1) { | |
| 112 | const state = @cmpxchgWeak( | |
| 113 | State, | |
| 114 | &m.state, | |
| 115 | .unlocked, | |
| 116 | new_state, | |
| 117 | .Acquire, | |
| 118 | .Monotonic, | |
| 119 | ) orelse return; | |
| 120 | ||
| 121 | switch (state) { | |
| 122 | .unlocked => {}, | |
| 123 | .locked => {}, | |
| 124 | .waiting => break, | |
| 125 | } | |
| 126 | ||
| 127 | var iter = std.math.min(32, spin + 1); | |
| 128 | while (iter > 0) : (iter -= 1) | |
| 129 | std.Thread.spinLoopHint(); | |
| 130 | } | |
| 131 | ||
| 132 | new_state = .waiting; | |
| 133 | while (true) { | |
| 134 | switch (@atomicRmw(State, &m.state, .Xchg, new_state, .Acquire)) { | |
| 135 | .unlocked => return, | |
| 136 | else => {}, | |
| 137 | } | |
| 138 | switch (std.Target.current.os.tag) { | |
| 139 | .linux => { | |
| 140 | switch (linux.getErrno(linux.futex_wait( | |
| 141 | @ptrCast(*const i32, &m.state), | |
| 142 | linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT, | |
| 143 | @enumToInt(new_state), | |
| 144 | null, | |
| 145 | ))) { | |
| 146 | 0 => {}, | |
| 147 | std.os.EINTR => {}, | |
| 148 | std.os.EAGAIN => {}, | |
| 149 | else => unreachable, | |
| 150 | } | |
| 151 | }, | |
| 152 | else => std.Thread.spinLoopHint(), | |
| 153 | } | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 157 | fn unlockSlow(m: *AtomicMutex) void { | |
| 158 | @setCold(true); | |
| 159 | ||
| 160 | switch (std.Target.current.os.tag) { | |
| 161 | .linux => { | |
| 162 | switch (linux.getErrno(linux.futex_wake( | |
| 163 | @ptrCast(*const i32, &m.state), | |
| 164 | linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE, | |
| 165 | 1, | |
| 166 | ))) { | |
| 167 | 0 => {}, | |
| 168 | std.os.EFAULT => {}, | |
| 169 | else => unreachable, | |
| 170 | } | |
| 171 | }, | |
| 172 | else => {}, | |
| 173 | } | |
| 174 | } | |
| 175 | }; | |
| 176 | ||
| 177 | pub const PthreadMutex = struct { | |
| 178 | pthread_mutex: std.c.pthread_mutex_t = .{}, | |
| 179 | ||
| 180 | pub const Held = struct { | |
| 181 | mutex: *PthreadMutex, | |
| 182 | ||
| 183 | pub fn release(held: Held) void { | |
| 184 | switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) { | |
| 185 | 0 => return, | |
| 186 | std.c.EINVAL => unreachable, | |
| 187 | std.c.EAGAIN => unreachable, | |
| 188 | std.c.EPERM => unreachable, | |
| 189 | else => unreachable, | |
| 190 | } | |
| 191 | } | |
| 192 | }; | |
| 193 | ||
| 194 | /// Try to acquire the mutex without blocking. Returns null if | |
| 195 | /// the mutex is unavailable. Otherwise returns Held. Call | |
| 196 | /// release on Held. | |
| 197 | pub fn tryAcquire(m: *PthreadMutex) ?Held { | |
| 198 | if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == 0) { | |
| 199 | return Held{ .mutex = m }; | |
| 200 | } else { | |
| 201 | return null; | |
| 202 | } | |
| 203 | } | |
| 204 | ||
| 205 | /// Acquire the mutex. Will deadlock if the mutex is already | |
| 206 | /// held by the calling thread. | |
| 207 | pub fn acquire(m: *PthreadMutex) Held { | |
| 208 | switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) { | |
| 209 | 0 => return Held{ .mutex = m }, | |
| 210 | std.c.EINVAL => unreachable, | |
| 211 | std.c.EBUSY => unreachable, | |
| 212 | std.c.EAGAIN => unreachable, | |
| 213 | std.c.EDEADLK => unreachable, | |
| 214 | std.c.EPERM => unreachable, | |
| 215 | else => unreachable, | |
| 216 | } | |
| 217 | } | |
| 218 | }; | |
| 219 | ||
| 220 | /// This has the sematics as `Mutex`, however it does not actually do any | |
| 221 | /// synchronization. Operations are safety-checked no-ops. | |
| 222 | pub const Dummy = struct { | |
| 223 | lock: @TypeOf(lock_init) = lock_init, | |
| 224 | ||
| 225 | const lock_init = if (std.debug.runtime_safety) false else {}; | |
| 226 | ||
| 227 | pub const Held = struct { | |
| 228 | mutex: *Dummy, | |
| 229 | ||
| 230 | pub fn release(held: Held) void { | |
| 231 | if (std.debug.runtime_safety) { | |
| 232 | held.mutex.lock = false; | |
| 233 | } | |
| 234 | } | |
| 235 | }; | |
| 236 | ||
| 237 | /// Try to acquire the mutex without blocking. Returns null if | |
| 238 | /// the mutex is unavailable. Otherwise returns Held. Call | |
| 239 | /// release on Held. | |
| 240 | pub fn tryAcquire(m: *Dummy) ?Held { | |
| 241 | if (std.debug.runtime_safety) { | |
| 242 | if (m.lock) return null; | |
| 243 | m.lock = true; | |
| 244 | } | |
| 245 | return Held{ .mutex = m }; | |
| 246 | } | |
| 247 | ||
| 248 | /// Acquire the mutex. Will deadlock if the mutex is already | |
| 249 | /// held by the calling thread. | |
| 250 | pub fn acquire(m: *Dummy) Held { | |
| 251 | return m.tryAcquire() orelse @panic("deadlock detected"); | |
| 252 | } | |
| 253 | }; | |
| 254 | ||
| 255 | const WindowsMutex = struct { | |
| 256 | srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT, | |
| 257 | ||
| 258 | pub const Held = struct { | |
| 259 | mutex: *WindowsMutex, | |
| 260 | ||
| 261 | pub fn release(held: Held) void { | |
| 262 | windows.kernel32.ReleaseSRWLockExclusive(&held.mutex.srwlock); | |
| 263 | } | |
| 264 | }; | |
| 265 | ||
| 266 | pub fn tryAcquire(m: *WindowsMutex) ?Held { | |
| 267 | if (windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) { | |
| 268 | return Held{ .mutex = m }; | |
| 269 | } else { | |
| 270 | return null; | |
| 271 | } | |
| 272 | } | |
| 273 | ||
| 274 | pub fn acquire(m: *WindowsMutex) Held { | |
| 275 | windows.kernel32.AcquireSRWLockExclusive(&m.srwlock); | |
| 276 | return Held{ .mutex = m }; | |
| 277 | } | |
| 278 | }; | |
| 279 | ||
| 280 | const TestContext = struct { | |
| 281 | mutex: *Mutex, | |
| 282 | data: i128, | |
| 283 | ||
| 284 | const incr_count = 10000; | |
| 285 | }; | |
| 286 | ||
| 287 | test "basic usage" { | |
| 288 | var mutex = Mutex{}; | |
| 289 | ||
| 290 | var context = TestContext{ | |
| 291 | .mutex = &mutex, | |
| 292 | .data = 0, | |
| 293 | }; | |
| 294 | ||
| 295 | if (builtin.single_threaded) { | |
| 296 | worker(&context); | |
| 297 | testing.expect(context.data == TestContext.incr_count); | |
| 298 | } else { | |
| 299 | const thread_count = 10; | |
| 300 | var threads: [thread_count]*std.Thread = undefined; | |
| 301 | for (threads) |*t| { | |
| 302 | t.* = try std.Thread.spawn(&context, worker); | |
| 303 | } | |
| 304 | for (threads) |t| | |
| 305 | t.wait(); | |
| 306 | ||
| 307 | testing.expect(context.data == thread_count * TestContext.incr_count); | |
| 308 | } | |
| 309 | } | |
| 310 | ||
| 311 | fn worker(ctx: *TestContext) void { | |
| 312 | var i: usize = 0; | |
| 313 | while (i != TestContext.incr_count) : (i += 1) { | |
| 314 | const held = ctx.mutex.acquire(); | |
| 315 | defer held.release(); | |
| 316 | ||
| 317 | ctx.data += 1; | |
| 318 | } | |
| 319 | } |
lib/std/Thread/ResetEvent.zig created+297| ... | ... | @@ -0,0 +1,297 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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.Thread.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.Thread.StaticResetEvent.DebugEvent | |
| 28 | else if (std.Target.current.isDarwin()) | |
| 29 | DarwinEvent | |
| 30 | else if (std.Thread.use_pthreads) | |
| 31 | PosixEvent | |
| 32 | else | |
| 33 | std.Thread.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/Thread/RwLock.zig created+308| ... | ... | @@ -0,0 +1,308 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 lock that supports one writer or many readers. | |
| 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 | ||
| 12 | impl: Impl, | |
| 13 | ||
| 14 | const RwLock = @This(); | |
| 15 | const std = @import("../std.zig"); | |
| 16 | const builtin = std.builtin; | |
| 17 | const assert = std.debug.assert; | |
| 18 | const Mutex = std.Thread.Mutex; | |
| 19 | const Semaphore = std.Semaphore; | |
| 20 | const CondVar = std.CondVar; | |
| 21 | ||
| 22 | pub const Impl = if (builtin.single_threaded) | |
| 23 | SingleThreadedRwLock | |
| 24 | else if (std.Thread.use_pthreads) | |
| 25 | PthreadRwLock | |
| 26 | else | |
| 27 | DefaultRwLock; | |
| 28 | ||
| 29 | pub fn init(rwl: *RwLock) void { | |
| 30 | return rwl.impl.init(); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn deinit(rwl: *RwLock) void { | |
| 34 | return rwl.impl.deinit(); | |
| 35 | } | |
| 36 | ||
| 37 | /// Attempts to obtain exclusive lock ownership. | |
| 38 | /// Returns `true` if the lock is obtained, `false` otherwise. | |
| 39 | pub fn tryLock(rwl: *RwLock) bool { | |
| 40 | return rwl.impl.tryLock(); | |
| 41 | } | |
| 42 | ||
| 43 | /// Blocks until exclusive lock ownership is acquired. | |
| 44 | pub fn lock(rwl: *RwLock) void { | |
| 45 | return rwl.impl.lock(); | |
| 46 | } | |
| 47 | ||
| 48 | /// Releases a held exclusive lock. | |
| 49 | /// Asserts the lock is held exclusively. | |
| 50 | pub fn unlock(rwl: *RwLock) void { | |
| 51 | return rwl.impl.unlock(); | |
| 52 | } | |
| 53 | ||
| 54 | /// Attempts to obtain shared lock ownership. | |
| 55 | /// Returns `true` if the lock is obtained, `false` otherwise. | |
| 56 | pub fn tryLockShared(rwl: *RwLock) bool { | |
| 57 | return rwl.impl.tryLockShared(); | |
| 58 | } | |
| 59 | ||
| 60 | /// Blocks until shared lock ownership is acquired. | |
| 61 | pub fn lockShared(rwl: *RwLock) void { | |
| 62 | return rwl.impl.lockShared(); | |
| 63 | } | |
| 64 | ||
| 65 | /// Releases a held shared lock. | |
| 66 | pub fn unlockShared(rwl: *RwLock) void { | |
| 67 | return rwl.impl.unlockShared(); | |
| 68 | } | |
| 69 | ||
| 70 | /// Single-threaded applications use this for deadlock checks in | |
| 71 | /// debug mode, and no-ops in release modes. | |
| 72 | pub const SingleThreadedRwLock = struct { | |
| 73 | state: enum { unlocked, locked_exclusive, locked_shared }, | |
| 74 | shared_count: usize, | |
| 75 | ||
| 76 | pub fn init(rwl: *SingleThreadedRwLock) void { | |
| 77 | rwl.* = .{ | |
| 78 | .state = .unlocked, | |
| 79 | .shared_count = 0, | |
| 80 | }; | |
| 81 | } | |
| 82 | ||
| 83 | pub fn deinit(rwl: *SingleThreadedRwLock) void { | |
| 84 | assert(rwl.state == .unlocked); | |
| 85 | assert(rwl.shared_count == 0); | |
| 86 | } | |
| 87 | ||
| 88 | /// Attempts to obtain exclusive lock ownership. | |
| 89 | /// Returns `true` if the lock is obtained, `false` otherwise. | |
| 90 | pub fn tryLock(rwl: *SingleThreadedRwLock) bool { | |
| 91 | switch (rwl.state) { | |
| 92 | .unlocked => { | |
| 93 | assert(rwl.shared_count == 0); | |
| 94 | rwl.state = .locked_exclusive; | |
| 95 | return true; | |
| 96 | }, | |
| 97 | .locked_exclusive, .locked_shared => return false, | |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | /// Blocks until exclusive lock ownership is acquired. | |
| 102 | pub fn lock(rwl: *SingleThreadedRwLock) void { | |
| 103 | assert(rwl.state == .unlocked); // deadlock detected | |
| 104 | assert(rwl.shared_count == 0); // corrupted state detected | |
| 105 | rwl.state = .locked_exclusive; | |
| 106 | } | |
| 107 | ||
| 108 | /// Releases a held exclusive lock. | |
| 109 | /// Asserts the lock is held exclusively. | |
| 110 | pub fn unlock(rwl: *SingleThreadedRwLock) void { | |
| 111 | assert(rwl.state == .locked_exclusive); | |
| 112 | assert(rwl.shared_count == 0); // corrupted state detected | |
| 113 | rwl.state = .unlocked; | |
| 114 | } | |
| 115 | ||
| 116 | /// Attempts to obtain shared lock ownership. | |
| 117 | /// Returns `true` if the lock is obtained, `false` otherwise. | |
| 118 | pub fn tryLockShared(rwl: *SingleThreadedRwLock) bool { | |
| 119 | switch (rwl.state) { | |
| 120 | .unlocked => { | |
| 121 | rwl.state = .locked_shared; | |
| 122 | assert(rwl.shared_count == 0); | |
| 123 | rwl.shared_count = 1; | |
| 124 | return true; | |
| 125 | }, | |
| 126 | .locked_exclusive, .locked_shared => return false, | |
| 127 | } | |
| 128 | } | |
| 129 | ||
| 130 | /// Blocks until shared lock ownership is acquired. | |
| 131 | pub fn lockShared(rwl: *SingleThreadedRwLock) void { | |
| 132 | switch (rwl.state) { | |
| 133 | .unlocked => { | |
| 134 | rwl.state = .locked_shared; | |
| 135 | assert(rwl.shared_count == 0); | |
| 136 | rwl.shared_count = 1; | |
| 137 | }, | |
| 138 | .locked_shared => { | |
| 139 | rwl.shared_count += 1; | |
| 140 | }, | |
| 141 | .locked_exclusive => unreachable, // deadlock detected | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | /// Releases a held shared lock. | |
| 146 | pub fn unlockShared(rwl: *SingleThreadedRwLock) void { | |
| 147 | switch (rwl.state) { | |
| 148 | .unlocked => unreachable, // too many calls to `unlockShared` | |
| 149 | .locked_exclusive => unreachable, // exclusively held lock | |
| 150 | .locked_shared => { | |
| 151 | rwl.shared_count -= 1; | |
| 152 | if (rwl.shared_count == 0) { | |
| 153 | rwl.state = .unlocked; | |
| 154 | } | |
| 155 | }, | |
| 156 | } | |
| 157 | } | |
| 158 | }; | |
| 159 | ||
| 160 | pub const PthreadRwLock = struct { | |
| 161 | rwlock: pthread_rwlock_t, | |
| 162 | ||
| 163 | pub fn init(rwl: *PthreadRwLock) void { | |
| 164 | rwl.* = .{ .rwlock = .{} }; | |
| 165 | } | |
| 166 | ||
| 167 | pub fn deinit(rwl: *PthreadRwLock) void { | |
| 168 | const safe_rc = switch (std.builtin.os.tag) { | |
| 169 | .dragonfly, .netbsd => std.os.EAGAIN, | |
| 170 | else => 0, | |
| 171 | }; | |
| 172 | ||
| 173 | const rc = std.c.pthread_rwlock_destroy(&rwl.rwlock); | |
| 174 | assert(rc == 0 or rc == safe_rc); | |
| 175 | ||
| 176 | rwl.* = undefined; | |
| 177 | } | |
| 178 | ||
| 179 | pub fn tryLock(rwl: *PthreadRwLock) bool { | |
| 180 | return pthread_rwlock_trywrlock(&rwl.rwlock) == 0; | |
| 181 | } | |
| 182 | ||
| 183 | pub fn lock(rwl: *PthreadRwLock) void { | |
| 184 | const rc = pthread_rwlock_wrlock(&rwl.rwlock); | |
| 185 | assert(rc == 0); | |
| 186 | } | |
| 187 | ||
| 188 | pub fn unlock(rwl: *PthreadRwLock) void { | |
| 189 | const rc = pthread_rwlock_unlock(&rwl.rwlock); | |
| 190 | assert(rc == 0); | |
| 191 | } | |
| 192 | ||
| 193 | pub fn tryLockShared(rwl: *PthreadRwLock) bool { | |
| 194 | return pthread_rwlock_tryrdlock(&rwl.rwlock) == 0; | |
| 195 | } | |
| 196 | ||
| 197 | pub fn lockShared(rwl: *PthreadRwLock) void { | |
| 198 | const rc = pthread_rwlock_rdlock(&rwl.rwlock); | |
| 199 | assert(rc == 0); | |
| 200 | } | |
| 201 | ||
| 202 | pub fn unlockShared(rwl: *PthreadRwLock) void { | |
| 203 | const rc = pthread_rwlock_unlock(&rwl.rwlock); | |
| 204 | assert(rc == 0); | |
| 205 | } | |
| 206 | }; | |
| 207 | ||
| 208 | pub const DefaultRwLock = struct { | |
| 209 | state: usize, | |
| 210 | mutex: Mutex, | |
| 211 | semaphore: Semaphore, | |
| 212 | ||
| 213 | const IS_WRITING: usize = 1; | |
| 214 | const WRITER: usize = 1 << 1; | |
| 215 | const READER: usize = 1 << (1 + std.meta.bitCount(Count)); | |
| 216 | const WRITER_MASK: usize = std.math.maxInt(Count) << @ctz(usize, WRITER); | |
| 217 | const READER_MASK: usize = std.math.maxInt(Count) << @ctz(usize, READER); | |
| 218 | const Count = std.meta.Int(.unsigned, @divFloor(std.meta.bitCount(usize) - 1, 2)); | |
| 219 | ||
| 220 | pub fn init(rwl: *DefaultRwLock) void { | |
| 221 | rwl.* = .{ | |
| 222 | .state = 0, | |
| 223 | .mutex = Mutex.init(), | |
| 224 | .semaphore = Semaphore.init(0), | |
| 225 | }; | |
| 226 | } | |
| 227 | ||
| 228 | pub fn deinit(rwl: *DefaultRwLock) void { | |
| 229 | rwl.semaphore.deinit(); | |
| 230 | rwl.mutex.deinit(); | |
| 231 | rwl.* = undefined; | |
| 232 | } | |
| 233 | ||
| 234 | pub fn tryLock(rwl: *DefaultRwLock) bool { | |
| 235 | if (rwl.mutex.tryLock()) { | |
| 236 | const state = @atomicLoad(usize, &rwl.state, .SeqCst); | |
| 237 | if (state & READER_MASK == 0) { | |
| 238 | _ = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .SeqCst); | |
| 239 | return true; | |
| 240 | } | |
| 241 | ||
| 242 | rwl.mutex.unlock(); | |
| 243 | } | |
| 244 | ||
| 245 | return false; | |
| 246 | } | |
| 247 | ||
| 248 | pub fn lock(rwl: *DefaultRwLock) void { | |
| 249 | _ = @atomicRmw(usize, &rwl.state, .Add, WRITER, .SeqCst); | |
| 250 | rwl.mutex.lock(); | |
| 251 | ||
| 252 | const state = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .SeqCst); | |
| 253 | if (state & READER_MASK != 0) | |
| 254 | rwl.semaphore.wait(); | |
| 255 | } | |
| 256 | ||
| 257 | pub fn unlock(rwl: *DefaultRwLock) void { | |
| 258 | _ = @atomicRmw(usize, &rwl.state, .And, ~IS_WRITING, .SeqCst); | |
| 259 | rwl.mutex.unlock(); | |
| 260 | } | |
| 261 | ||
| 262 | pub fn tryLockShared(rwl: *DefaultRwLock) bool { | |
| 263 | const state = @atomicLoad(usize, &rwl.state, .SeqCst); | |
| 264 | if (state & (IS_WRITING | WRITER_MASK) == 0) { | |
| 265 | _ = @cmpxchgStrong( | |
| 266 | usize, | |
| 267 | &rwl.state, | |
| 268 | state, | |
| 269 | state + READER, | |
| 270 | .SeqCst, | |
| 271 | .SeqCst, | |
| 272 | ) orelse return true; | |
| 273 | } | |
| 274 | ||
| 275 | if (rwl.mutex.tryLock()) { | |
| 276 | _ = @atomicRmw(usize, &rwl.state, .Add, READER, .SeqCst); | |
| 277 | rwl.mutex.unlock(); | |
| 278 | return true; | |
| 279 | } | |
| 280 | ||
| 281 | return false; | |
| 282 | } | |
| 283 | ||
| 284 | pub fn lockShared(rwl: *DefaultRwLock) void { | |
| 285 | var state = @atomicLoad(usize, &rwl.state, .SeqCst); | |
| 286 | while (state & (IS_WRITING | WRITER_MASK) == 0) { | |
| 287 | state = @cmpxchgWeak( | |
| 288 | usize, | |
| 289 | &rwl.state, | |
| 290 | state, | |
| 291 | state + READER, | |
| 292 | .SeqCst, | |
| 293 | .SeqCst, | |
| 294 | ) orelse return; | |
| 295 | } | |
| 296 | ||
| 297 | rwl.mutex.lock(); | |
| 298 | _ = @atomicRmw(usize, &rwl.state, .Add, READER, .SeqCst); | |
| 299 | rwl.mutex.unlock(); | |
| 300 | } | |
| 301 | ||
| 302 | pub fn unlockShared(rwl: *DefaultRwLock) void { | |
| 303 | const state = @atomicRmw(usize, &rwl.state, .Sub, READER, .SeqCst); | |
| 304 | ||
| 305 | if ((state & READER_MASK == READER) and (state & IS_WRITING != 0)) | |
| 306 | rwl.semaphore.post(); | |
| 307 | } | |
| 308 | }; |
lib/std/Thread/Semaphore.zig created+39| ... | ... | @@ -0,0 +1,39 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 semaphore is an unsigned integer that blocks the kernel thread if | |
| 8 | //! the number would become negative. | |
| 9 | //! This API supports static initialization and does not require deinitialization. | |
| 10 | ||
| 11 | mutex: Mutex = .{}, | |
| 12 | cond: Condition = .{}, | |
| 13 | //! It is OK to initialize this field to any value. | |
| 14 | permits: usize = 0, | |
| 15 | ||
| 16 | const RwLock = @This(); | |
| 17 | const std = @import("../std.zig"); | |
| 18 | const Mutex = std.Thread.Mutex; | |
| 19 | const Condition = std.Thread.Condition; | |
| 20 | ||
| 21 | pub fn wait(sem: *Semaphore) void { | |
| 22 | const held = sem.mutex.acquire(); | |
| 23 | defer held.release(); | |
| 24 | ||
| 25 | while (sem.permits == 0) | |
| 26 | sem.cond.wait(&sem.mutex); | |
| 27 | ||
| 28 | sem.permits -= 1; | |
| 29 | if (sem.permits > 0) | |
| 30 | sem.cond.signal(); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn post(sem: *Semaphore) void { | |
| 34 | const held = sem.mutex.acquire(); | |
| 35 | defer held.release(); | |
| 36 | ||
| 37 | sem.permits += 1; | |
| 38 | sem.cond.signal(); | |
| 39 | } |
lib/std/Thread/StaticResetEvent.zig created+395| ... | ... | @@ -0,0 +1,395 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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.Thread.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 assert = std.debug.assert; | |
| 19 | const os = std.os; | |
| 20 | const time = std.time; | |
| 21 | const linux = std.os.linux; | |
| 22 | const windows = std.os.windows; | |
| 23 | const testing = std.testing; | |
| 24 | ||
| 25 | impl: Impl = .{}, | |
| 26 | ||
| 27 | pub const Impl = if (std.builtin.single_threaded) | |
| 28 | DebugEvent | |
| 29 | else | |
| 30 | AtomicEvent; | |
| 31 | ||
| 32 | /// Sets the event if not already set and wakes up all the threads waiting on | |
| 33 | /// the event. It is safe to call `set` multiple times before calling `wait`. | |
| 34 | /// However it is illegal to call `set` after `wait` is called until the event | |
| 35 | /// is `reset`. This function is thread-safe. | |
| 36 | pub fn set(ev: *StaticResetEvent) void { | |
| 37 | return ev.impl.set(); | |
| 38 | } | |
| 39 | ||
| 40 | /// Wait for the event to be set by blocking the current thread. | |
| 41 | /// Thread-safe. No spurious wakeups. | |
| 42 | /// Upon return from `wait`, the only function available to be called | |
| 43 | /// in `StaticResetEvent` is `reset`. | |
| 44 | pub fn wait(ev: *StaticResetEvent) void { | |
| 45 | return ev.impl.wait(); | |
| 46 | } | |
| 47 | ||
| 48 | /// Resets the event to its original, unset state. | |
| 49 | /// This function is *not* thread-safe. It is equivalent to calling | |
| 50 | /// `deinit` followed by `init` but without the possibility of failure. | |
| 51 | pub fn reset(ev: *StaticResetEvent) void { | |
| 52 | return ev.impl.reset(); | |
| 53 | } | |
| 54 | ||
| 55 | pub const TimedWaitResult = std.Thread.ResetEvent.TimedWaitResult; | |
| 56 | ||
| 57 | /// Wait for the event to be set by blocking the current thread. | |
| 58 | /// A timeout in nanoseconds can be provided as a hint for how | |
| 59 | /// long the thread should block on the unset event before returning | |
| 60 | /// `TimedWaitResult.timed_out`. | |
| 61 | /// Thread-safe. No precision of timing is guaranteed. | |
| 62 | /// Upon return from `timedWait`, the only function available to be called | |
| 63 | /// in `StaticResetEvent` is `reset`. | |
| 64 | pub fn timedWait(ev: *StaticResetEvent, timeout_ns: u64) TimedWaitResult { | |
| 65 | return ev.impl.timedWait(timeout_ns); | |
| 66 | } | |
| 67 | ||
| 68 | /// For single-threaded builds, we use this to detect deadlocks. | |
| 69 | /// In unsafe modes this ends up being no-ops. | |
| 70 | pub const DebugEvent = struct { | |
| 71 | state: State = State.unset, | |
| 72 | ||
| 73 | const State = enum { | |
| 74 | unset, | |
| 75 | set, | |
| 76 | waited, | |
| 77 | }; | |
| 78 | ||
| 79 | /// This function is provided so that this type can be re-used inside | |
| 80 | /// `std.Thread.ResetEvent`. | |
| 81 | pub fn init(ev: *DebugEvent) void { | |
| 82 | ev.* = .{}; | |
| 83 | } | |
| 84 | ||
| 85 | /// This function is provided so that this type can be re-used inside | |
| 86 | /// `std.Thread.ResetEvent`. | |
| 87 | pub fn deinit(ev: *DebugEvent) void { | |
| 88 | ev.* = undefined; | |
| 89 | } | |
| 90 | ||
| 91 | pub fn set(ev: *DebugEvent) void { | |
| 92 | switch (ev.state) { | |
| 93 | .unset => ev.state = .set, | |
| 94 | .set => {}, | |
| 95 | .waited => unreachable, // Not allowed to call `set` until `reset`. | |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | pub fn wait(ev: *DebugEvent) void { | |
| 100 | switch (ev.state) { | |
| 101 | .unset => unreachable, // Deadlock detected. | |
| 102 | .set => return, | |
| 103 | .waited => unreachable, // Not allowed to call `wait` until `reset`. | |
| 104 | } | |
| 105 | } | |
| 106 | ||
| 107 | pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult { | |
| 108 | switch (ev.state) { | |
| 109 | .unset => return .timed_out, | |
| 110 | .set => return .event_set, | |
| 111 | .waited => unreachable, // Not allowed to call `wait` until `reset`. | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | pub fn reset(ev: *DebugEvent) void { | |
| 116 | ev.state = .unset; | |
| 117 | } | |
| 118 | }; | |
| 119 | ||
| 120 | pub const AtomicEvent = struct { | |
| 121 | waiters: u32 = 0, | |
| 122 | ||
| 123 | const WAKE = 1 << 0; | |
| 124 | const WAIT = 1 << 1; | |
| 125 | ||
| 126 | /// This function is provided so that this type can be re-used inside | |
| 127 | /// `std.Thread.ResetEvent`. | |
| 128 | pub fn init(ev: *AtomicEvent) void { | |
| 129 | ev.* = .{}; | |
| 130 | } | |
| 131 | ||
| 132 | /// This function is provided so that this type can be re-used inside | |
| 133 | /// `std.Thread.ResetEvent`. | |
| 134 | pub fn deinit(ev: *AtomicEvent) void { | |
| 135 | ev.* = undefined; | |
| 136 | } | |
| 137 | ||
| 138 | pub fn set(ev: *AtomicEvent) void { | |
| 139 | const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release); | |
| 140 | if (waiters >= WAIT) { | |
| 141 | return Futex.wake(&ev.waiters, waiters >> 1); | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | pub fn wait(ev: *AtomicEvent) void { | |
| 146 | switch (ev.timedWait(null)) { | |
| 147 | .timed_out => unreachable, | |
| 148 | .event_set => return, | |
| 149 | } | |
| 150 | } | |
| 151 | ||
| 152 | pub fn timedWait(ev: *AtomicEvent, timeout: ?u64) TimedWaitResult { | |
| 153 | var waiters = @atomicLoad(u32, &ev.waiters, .Acquire); | |
| 154 | while (waiters != WAKE) { | |
| 155 | waiters = @cmpxchgWeak(u32, &ev.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse { | |
| 156 | if (Futex.wait(&ev.waiters, timeout)) |_| { | |
| 157 | return .event_set; | |
| 158 | } else |_| { | |
| 159 | return .timed_out; | |
| 160 | } | |
| 161 | }; | |
| 162 | } | |
| 163 | return .event_set; | |
| 164 | } | |
| 165 | ||
| 166 | pub fn reset(ev: *AtomicEvent) void { | |
| 167 | @atomicStore(u32, &ev.waiters, 0, .Monotonic); | |
| 168 | } | |
| 169 | ||
| 170 | pub const Futex = switch (std.Target.current.os.tag) { | |
| 171 | .windows => WindowsFutex, | |
| 172 | .linux => LinuxFutex, | |
| 173 | else => SpinFutex, | |
| 174 | }; | |
| 175 | ||
| 176 | pub const SpinFutex = struct { | |
| 177 | fn wake(waiters: *u32, wake_count: u32) void {} | |
| 178 | ||
| 179 | fn wait(waiters: *u32, timeout: ?u64) !void { | |
| 180 | var timer: time.Timer = undefined; | |
| 181 | if (timeout != null) | |
| 182 | timer = time.Timer.start() catch return error.TimedOut; | |
| 183 | ||
| 184 | while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { | |
| 185 | std.os.sched_yield() catch std.Thread.spinLoopHint(); | |
| 186 | if (timeout) |timeout_ns| { | |
| 187 | if (timer.read() >= timeout_ns) | |
| 188 | return error.TimedOut; | |
| 189 | } | |
| 190 | } | |
| 191 | } | |
| 192 | }; | |
| 193 | ||
| 194 | pub const LinuxFutex = struct { | |
| 195 | fn wake(waiters: *u32, wake_count: u32) void { | |
| 196 | const waiting = std.math.maxInt(i32); // wake_count | |
| 197 | const ptr = @ptrCast(*const i32, waiters); | |
| 198 | const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting); | |
| 199 | assert(linux.getErrno(rc) == 0); | |
| 200 | } | |
| 201 | ||
| 202 | fn wait(waiters: *u32, timeout: ?u64) !void { | |
| 203 | var ts: linux.timespec = undefined; | |
| 204 | var ts_ptr: ?*linux.timespec = null; | |
| 205 | if (timeout) |timeout_ns| { | |
| 206 | ts_ptr = &ts; | |
| 207 | ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s); | |
| 208 | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); | |
| 209 | } | |
| 210 | ||
| 211 | while (true) { | |
| 212 | const waiting = @atomicLoad(u32, waiters, .Acquire); | |
| 213 | if (waiting == WAKE) | |
| 214 | return; | |
| 215 | const expected = @intCast(i32, waiting); | |
| 216 | const ptr = @ptrCast(*const i32, waiters); | |
| 217 | const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr); | |
| 218 | switch (linux.getErrno(rc)) { | |
| 219 | 0 => continue, | |
| 220 | os.ETIMEDOUT => return error.TimedOut, | |
| 221 | os.EINTR => continue, | |
| 222 | os.EAGAIN => return, | |
| 223 | else => unreachable, | |
| 224 | } | |
| 225 | } | |
| 226 | } | |
| 227 | }; | |
| 228 | ||
| 229 | pub const WindowsFutex = struct { | |
| 230 | pub fn wake(waiters: *u32, wake_count: u32) void { | |
| 231 | const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count); | |
| 232 | const key = @ptrCast(*const c_void, waiters); | |
| 233 | ||
| 234 | var waiting = wake_count; | |
| 235 | while (waiting != 0) : (waiting -= 1) { | |
| 236 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); | |
| 237 | assert(rc == .SUCCESS); | |
| 238 | } | |
| 239 | } | |
| 240 | ||
| 241 | pub fn wait(waiters: *u32, timeout: ?u64) !void { | |
| 242 | const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout); | |
| 243 | const key = @ptrCast(*const c_void, waiters); | |
| 244 | ||
| 245 | // NT uses timeouts in units of 100ns with negative value being relative | |
| 246 | var timeout_ptr: ?*windows.LARGE_INTEGER = null; | |
| 247 | var timeout_value: windows.LARGE_INTEGER = undefined; | |
| 248 | if (timeout) |timeout_ns| { | |
| 249 | timeout_ptr = &timeout_value; | |
| 250 | timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100); | |
| 251 | } | |
| 252 | ||
| 253 | // NtWaitForKeyedEvent doesnt have spurious wake-ups | |
| 254 | var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); | |
| 255 | switch (rc) { | |
| 256 | .TIMEOUT => { | |
| 257 | // update the wait count to signal that we're not waiting anymore. | |
| 258 | // if the .set() thread already observed that we are, perform a | |
| 259 | // matching NtWaitForKeyedEvent so that the .set() thread doesn't | |
| 260 | // deadlock trying to run NtReleaseKeyedEvent above. | |
| 261 | var waiting = @atomicLoad(u32, waiters, .Monotonic); | |
| 262 | while (true) { | |
| 263 | if (waiting == WAKE) { | |
| 264 | rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); | |
| 265 | assert(rc == .WAIT_0); | |
| 266 | break; | |
| 267 | } else { | |
| 268 | waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break; | |
| 269 | continue; | |
| 270 | } | |
| 271 | } | |
| 272 | return error.TimedOut; | |
| 273 | }, | |
| 274 | .WAIT_0 => {}, | |
| 275 | else => unreachable, | |
| 276 | } | |
| 277 | } | |
| 278 | ||
| 279 | var event_handle: usize = EMPTY; | |
| 280 | const EMPTY = ~@as(usize, 0); | |
| 281 | const LOADING = EMPTY - 1; | |
| 282 | ||
| 283 | pub fn getEventHandle() ?windows.HANDLE { | |
| 284 | var handle = @atomicLoad(usize, &event_handle, .Monotonic); | |
| 285 | while (true) { | |
| 286 | switch (handle) { | |
| 287 | EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse { | |
| 288 | const handle_ptr = @ptrCast(*windows.HANDLE, &handle); | |
| 289 | const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; | |
| 290 | if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS) | |
| 291 | handle = 0; | |
| 292 | @atomicStore(usize, &event_handle, handle, .Monotonic); | |
| 293 | return @intToPtr(?windows.HANDLE, handle); | |
| 294 | }, | |
| 295 | LOADING => { | |
| 296 | std.os.sched_yield() catch std.Thread.spinLoopHint(); | |
| 297 | handle = @atomicLoad(usize, &event_handle, .Monotonic); | |
| 298 | }, | |
| 299 | else => { | |
| 300 | return @intToPtr(?windows.HANDLE, handle); | |
| 301 | }, | |
| 302 | } | |
| 303 | } | |
| 304 | } | |
| 305 | }; | |
| 306 | }; | |
| 307 | ||
| 308 | test "basic usage" { | |
| 309 | var event = StaticResetEvent{}; | |
| 310 | ||
| 311 | // test event setting | |
| 312 | event.set(); | |
| 313 | ||
| 314 | // test event resetting | |
| 315 | event.reset(); | |
| 316 | ||
| 317 | // test event waiting (non-blocking) | |
| 318 | event.set(); | |
| 319 | event.wait(); | |
| 320 | event.reset(); | |
| 321 | ||
| 322 | event.set(); | |
| 323 | testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1)); | |
| 324 | ||
| 325 | // test cross-thread signaling | |
| 326 | if (std.builtin.single_threaded) | |
| 327 | return; | |
| 328 | ||
| 329 | const Context = struct { | |
| 330 | const Self = @This(); | |
| 331 | ||
| 332 | value: u128 = 0, | |
| 333 | in: StaticResetEvent = .{}, | |
| 334 | out: StaticResetEvent = .{}, | |
| 335 | ||
| 336 | fn sender(self: *Self) void { | |
| 337 | // update value and signal input | |
| 338 | testing.expect(self.value == 0); | |
| 339 | self.value = 1; | |
| 340 | self.in.set(); | |
| 341 | ||
| 342 | // wait for receiver to update value and signal output | |
| 343 | self.out.wait(); | |
| 344 | testing.expect(self.value == 2); | |
| 345 | ||
| 346 | // update value and signal final input | |
| 347 | self.value = 3; | |
| 348 | self.in.set(); | |
| 349 | } | |
| 350 | ||
| 351 | fn receiver(self: *Self) void { | |
| 352 | // wait for sender to update value and signal input | |
| 353 | self.in.wait(); | |
| 354 | assert(self.value == 1); | |
| 355 | ||
| 356 | // update value and signal output | |
| 357 | self.in.reset(); | |
| 358 | self.value = 2; | |
| 359 | self.out.set(); | |
| 360 | ||
| 361 | // wait for sender to update value and signal final input | |
| 362 | self.in.wait(); | |
| 363 | assert(self.value == 3); | |
| 364 | } | |
| 365 | ||
| 366 | fn sleeper(self: *Self) void { | |
| 367 | self.in.set(); | |
| 368 | time.sleep(time.ns_per_ms * 2); | |
| 369 | self.value = 5; | |
| 370 | self.out.set(); | |
| 371 | } | |
| 372 | ||
| 373 | fn timedWaiter(self: *Self) !void { | |
| 374 | self.in.wait(); | |
| 375 | testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us)); | |
| 376 | try self.out.timedWait(time.ns_per_ms * 100); | |
| 377 | testing.expect(self.value == 5); | |
| 378 | } | |
| 379 | }; | |
| 380 | ||
| 381 | var context = Context{}; | |
| 382 | const receiver = try std.Thread.spawn(&context, Context.receiver); | |
| 383 | defer receiver.wait(); | |
| 384 | context.sender(); | |
| 385 | ||
| 386 | if (false) { | |
| 387 | // I have now observed this fail on macOS, Windows, and Linux. | |
| 388 | // https://github.com/ziglang/zig/issues/7009 | |
| 389 | var timed = Context.init(); | |
| 390 | defer timed.deinit(); | |
| 391 | const sleeper = try std.Thread.spawn(&timed, Context.sleeper); | |
| 392 | defer sleeper.wait(); | |
| 393 | try timed.timedWaiter(); | |
| 394 | } | |
| 395 | } |
lib/std/atomic/queue.zig+2-2| ... | ... | @@ -16,7 +16,7 @@ pub fn Queue(comptime T: type) type { |
| 16 | 16 | return struct { |
| 17 | 17 | head: ?*Node, |
| 18 | 18 | tail: ?*Node, |
| 19 | mutex: std.Mutex, | |
| 19 | mutex: std.Thread.Mutex, | |
| 20 | 20 | |
| 21 | 21 | pub const Self = @This(); |
| 22 | 22 | pub const Node = std.TailQueue(T).Node; |
| ... | ... | @@ -27,7 +27,7 @@ pub fn Queue(comptime T: type) type { |
| 27 | 27 | return Self{ |
| 28 | 28 | .head = null, |
| 29 | 29 | .tail = null, |
| 30 | .mutex = std.Mutex{}, | |
| 30 | .mutex = std.Thread.Mutex{}, | |
| 31 | 31 | }; |
| 32 | 32 | } |
| 33 | 33 |
lib/std/auto_reset_event.zig deleted-226| ... | ... | @@ -1,226 +0,0 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 assert = std.debug.assert; | |
| 10 | const StaticResetEvent = std.StaticResetEvent; | |
| 11 | ||
| 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). | |
| 14 | pub const AutoResetEvent = struct { | |
| 15 | /// AutoResetEvent has 3 possible states: | |
| 16 | /// - UNSET: the AutoResetEvent is currently unset | |
| 17 | /// - SET: the AutoResetEvent was notified before a wait() was called | |
| 18 | /// - <StaticResetEvent pointer>: there is an active waiter waiting for a notification. | |
| 19 | /// | |
| 20 | /// When attempting to wait: | |
| 21 | /// if the event is unset, it registers a ResetEvent pointer to be notified when the event is set | |
| 22 | /// if the event is already set, then it consumes the notification and resets the event. | |
| 23 | /// | |
| 24 | /// When attempting to notify: | |
| 25 | /// if the event is unset, then we set the event | |
| 26 | /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent | |
| 27 | /// | |
| 28 | /// This ensures that the event is automatically reset after a wait() has been issued | |
| 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) | |
| 36 | state: usize = UNSET, | |
| 37 | ||
| 38 | const UNSET = 0; | |
| 39 | const SET = 1; | |
| 40 | ||
| 41 | /// the minimum alignment for the `*StaticResetEvent` created by wait*() | |
| 42 | const event_align = std.math.max(@alignOf(StaticResetEvent), 2); | |
| 43 | ||
| 44 | pub fn wait(self: *AutoResetEvent) void { | |
| 45 | self.waitFor(null) catch unreachable; | |
| 46 | } | |
| 47 | ||
| 48 | pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { | |
| 49 | return self.waitFor(timeout); | |
| 50 | } | |
| 51 | ||
| 52 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { | |
| 53 | // lazily initialized StaticResetEvent | |
| 54 | var reset_event: StaticResetEvent align(event_align) = undefined; | |
| 55 | var has_reset_event = false; | |
| 56 | ||
| 57 | var state = @atomicLoad(usize, &self.state, .SeqCst); | |
| 58 | while (true) { | |
| 59 | // consume a notification if there is any | |
| 60 | if (state == SET) { | |
| 61 | @atomicStore(usize, &self.state, UNSET, .SeqCst); | |
| 62 | return; | |
| 63 | } | |
| 64 | ||
| 65 | // check if theres currently a pending ResetEvent pointer already registered | |
| 66 | if (state != UNSET) { | |
| 67 | unreachable; // multiple waiting threads on the same AutoResetEvent | |
| 68 | } | |
| 69 | ||
| 70 | // lazily initialize the ResetEvent if it hasn't been already | |
| 71 | if (!has_reset_event) { | |
| 72 | has_reset_event = true; | |
| 73 | reset_event = .{}; | |
| 74 | } | |
| 75 | ||
| 76 | // Since the AutoResetEvent currently isnt set, | |
| 77 | // try to register our ResetEvent on it to wait | |
| 78 | // for a set() call from another thread. | |
| 79 | if (@cmpxchgWeak( | |
| 80 | usize, | |
| 81 | &self.state, | |
| 82 | UNSET, | |
| 83 | @ptrToInt(&reset_event), | |
| 84 | .SeqCst, | |
| 85 | .SeqCst, | |
| 86 | )) |new_state| { | |
| 87 | state = new_state; | |
| 88 | continue; | |
| 89 | } | |
| 90 | ||
| 91 | // if no timeout was specified, then just wait forever | |
| 92 | const timeout_ns = timeout orelse { | |
| 93 | reset_event.wait(); | |
| 94 | return; | |
| 95 | }; | |
| 96 | ||
| 97 | // wait with a timeout and return if signalled via set() | |
| 98 | switch (reset_event.timedWait(timeout_ns)) { | |
| 99 | .event_set => return, | |
| 100 | .timed_out => {}, | |
| 101 | } | |
| 102 | ||
| 103 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. | |
| 104 | // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. | |
| 105 | state = @cmpxchgStrong( | |
| 106 | usize, | |
| 107 | &self.state, | |
| 108 | @ptrToInt(&reset_event), | |
| 109 | UNSET, | |
| 110 | .SeqCst, | |
| 111 | .SeqCst, | |
| 112 | ) orelse return error.TimedOut; | |
| 113 | ||
| 114 | // We didn't manage to unregister ourselves from the state. | |
| 115 | if (state == SET) { | |
| 116 | unreachable; // AutoResetEvent notified without waking up the waiting thread | |
| 117 | } else if (state != UNSET) { | |
| 118 | unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out | |
| 119 | } | |
| 120 | ||
| 121 | // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up. | |
| 122 | // We need to wait for it to wake up our ResetEvent before we can return and invalidate it. | |
| 123 | // We don't return error.TimedOut here as it technically notified us while we were "timing out". | |
| 124 | reset_event.wait(); | |
| 125 | return; | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | pub fn set(self: *AutoResetEvent) void { | |
| 130 | var state = @atomicLoad(usize, &self.state, .SeqCst); | |
| 131 | while (true) { | |
| 132 | // If the AutoResetEvent is already set, there is nothing else left to do | |
| 133 | if (state == SET) { | |
| 134 | return; | |
| 135 | } | |
| 136 | ||
| 137 | // If the AutoResetEvent isn't set, | |
| 138 | // then try to leave a notification for the wait() thread that we set() it. | |
| 139 | if (state == UNSET) { | |
| 140 | state = @cmpxchgWeak( | |
| 141 | usize, | |
| 142 | &self.state, | |
| 143 | UNSET, | |
| 144 | SET, | |
| 145 | .SeqCst, | |
| 146 | .SeqCst, | |
| 147 | ) orelse return; | |
| 148 | continue; | |
| 149 | } | |
| 150 | ||
| 151 | // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting. | |
| 152 | // Try to acquire ownership of it so that we can wake it up. | |
| 153 | // This also resets the AutoResetEvent so that there is no race condition as defined above. | |
| 154 | if (@cmpxchgWeak( | |
| 155 | usize, | |
| 156 | &self.state, | |
| 157 | state, | |
| 158 | UNSET, | |
| 159 | .SeqCst, | |
| 160 | .SeqCst, | |
| 161 | )) |new_state| { | |
| 162 | state = new_state; | |
| 163 | continue; | |
| 164 | } | |
| 165 | ||
| 166 | const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state); | |
| 167 | reset_event.set(); | |
| 168 | return; | |
| 169 | } | |
| 170 | } | |
| 171 | }; | |
| 172 | ||
| 173 | test "std.AutoResetEvent" { | |
| 174 | // test local code paths | |
| 175 | { | |
| 176 | var event = AutoResetEvent{}; | |
| 177 | testing.expectError(error.TimedOut, event.timedWait(1)); | |
| 178 | event.set(); | |
| 179 | event.wait(); | |
| 180 | } | |
| 181 | ||
| 182 | // test cross-thread signaling | |
| 183 | if (builtin.single_threaded) | |
| 184 | return; | |
| 185 | ||
| 186 | const Context = struct { | |
| 187 | value: u128 = 0, | |
| 188 | in: AutoResetEvent = AutoResetEvent{}, | |
| 189 | out: AutoResetEvent = AutoResetEvent{}, | |
| 190 | ||
| 191 | const Self = @This(); | |
| 192 | ||
| 193 | fn sender(self: *Self) void { | |
| 194 | testing.expect(self.value == 0); | |
| 195 | self.value = 1; | |
| 196 | self.out.set(); | |
| 197 | ||
| 198 | self.in.wait(); | |
| 199 | testing.expect(self.value == 2); | |
| 200 | self.value = 3; | |
| 201 | self.out.set(); | |
| 202 | ||
| 203 | self.in.wait(); | |
| 204 | testing.expect(self.value == 4); | |
| 205 | } | |
| 206 | ||
| 207 | fn receiver(self: *Self) void { | |
| 208 | self.out.wait(); | |
| 209 | testing.expect(self.value == 1); | |
| 210 | self.value = 2; | |
| 211 | self.in.set(); | |
| 212 | ||
| 213 | self.out.wait(); | |
| 214 | testing.expect(self.value == 3); | |
| 215 | self.value = 4; | |
| 216 | self.in.set(); | |
| 217 | } | |
| 218 | }; | |
| 219 | ||
| 220 | var context = Context{}; | |
| 221 | const send_thread = try std.Thread.spawn(&context, Context.sender); | |
| 222 | const recv_thread = try std.Thread.spawn(&context, Context.receiver); | |
| 223 | ||
| 224 | send_thread.wait(); | |
| 225 | recv_thread.wait(); | |
| 226 | } |
lib/std/c.zig+7| ... | ... | @@ -338,6 +338,13 @@ pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) c_int; |
| 338 | 338 | pub extern "c" fn pthread_cond_broadcast(cond: *pthread_cond_t) c_int; |
| 339 | 339 | pub extern "c" fn pthread_cond_destroy(cond: *pthread_cond_t) c_int; |
| 340 | 340 | |
| 341 | pub extern "c" fn pthread_rwlock_destroy(rwl: *pthread_rwlock_t) callconv(.C) c_int; | |
| 342 | pub extern "c" fn pthread_rwlock_rdlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; | |
| 343 | pub extern "c" fn pthread_rwlock_wrlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; | |
| 344 | pub extern "c" fn pthread_rwlock_tryrdlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; | |
| 345 | pub extern "c" fn pthread_rwlock_trywrlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; | |
| 346 | pub extern "c" fn pthread_rwlock_unlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; | |
| 347 | ||
| 341 | 348 | pub const pthread_t = *opaque {}; |
| 342 | 349 | pub const FILE = opaque {}; |
| 343 | 350 |
lib/std/c/darwin.zig+5-1| ... | ... | @@ -177,6 +177,10 @@ 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 pthread_rwlock_t = extern struct { | |
| 181 | __sig: c_long = 0x2DA8B3B4, | |
| 182 | __opaque: [192]u8 = [_]u8{0} ** 192, | |
| 183 | }; | |
| 180 | 184 | pub const sem_t = c_int; |
| 181 | 185 | const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40; |
| 182 | 186 | const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24; |
| ... | ... | @@ -192,7 +196,7 @@ pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int |
| 192 | 196 | pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; |
| 193 | 197 | |
| 194 | 198 | // Grand Central Dispatch is exposed by libSystem. |
| 195 | pub const dispatch_semaphore_t = *opaque{}; | |
| 199 | pub const dispatch_semaphore_t = *opaque {}; | |
| 196 | 200 | pub const dispatch_time_t = u64; |
| 197 | 201 | pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0); |
| 198 | 202 | pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0); |
lib/std/c/emscripten.zig+3| ... | ... | @@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct { |
| 9 | 9 | pub const pthread_cond_t = extern struct { |
| 10 | 10 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, |
| 11 | 11 | }; |
| 12 | pub const pthread_rwlock_t = extern struct { | |
| 13 | size: [32]u8 align(4) = [_]u8{0} ** 32, | |
| 14 | }; | |
| 12 | 15 | const __SIZEOF_PTHREAD_COND_T = 48; |
| 13 | 16 | const __SIZEOF_PTHREAD_MUTEX_T = 28; |
lib/std/c/freebsd.zig+3| ... | ... | @@ -43,6 +43,9 @@ pub const pthread_mutex_t = extern struct { |
| 43 | 43 | pub const pthread_cond_t = extern struct { |
| 44 | 44 | inner: ?*c_void = null, |
| 45 | 45 | }; |
| 46 | pub const pthread_rwlock_t = extern struct { | |
| 47 | ptr: ?*c_void = null, | |
| 48 | }; | |
| 46 | 49 | |
| 47 | 50 | pub const pthread_attr_t = extern struct { |
| 48 | 51 | __size: [56]u8, |
lib/std/c/fuchsia.zig+3| ... | ... | @@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct { |
| 9 | 9 | pub const pthread_cond_t = extern struct { |
| 10 | 10 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, |
| 11 | 11 | }; |
| 12 | pub const pthread_rwlock_t = extern struct { | |
| 13 | size: [56]u8 align(@alignOf(usize)) = [_]u8{0} ** 56, | |
| 14 | }; | |
| 12 | 15 | const __SIZEOF_PTHREAD_COND_T = 48; |
| 13 | 16 | const __SIZEOF_PTHREAD_MUTEX_T = 40; |
lib/std/c/haiku.zig+9| ... | ... | @@ -17,3 +17,12 @@ pub const pthread_cond_t = extern struct { |
| 17 | 17 | waiter_count: i32 = 0, |
| 18 | 18 | lock: i32 = 0, |
| 19 | 19 | }; |
| 20 | pub const pthread_rwlock_t = extern struct { | |
| 21 | flags: u32 = 0, | |
| 22 | owner: i32 = -1, | |
| 23 | lock_sem: i32 = 0, | |
| 24 | lock_count: i32 = 0, | |
| 25 | reader_count: i32 = 0, | |
| 26 | writer_count: i32 = 0, | |
| 27 | waiters: [2]?*c_void = [_]?*c_void{ null, null }, | |
| 28 | }; |
lib/std/c/hermit.zig+3| ... | ... | @@ -9,3 +9,6 @@ pub const pthread_mutex_t = extern struct { |
| 9 | 9 | pub const pthread_cond_t = extern struct { |
| 10 | 10 | inner: usize = ~@as(usize, 0), |
| 11 | 11 | }; |
| 12 | pub const pthread_rwlock_t = extern struct { | |
| 13 | ptr: usize = std.math.maxInt(usize), | |
| 14 | }; |
lib/std/c/linux.zig+26| ... | ... | @@ -123,6 +123,32 @@ 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 pthread_rwlock_t = switch (std.builtin.abi) { | |
| 127 | .android => switch (@sizeOf(usize)) { | |
| 128 | 4 => extern struct { | |
| 129 | lock: std.c.pthread_mutex_t = std.c.PTHREAD_MUTEX_INITIALIZER, | |
| 130 | cond: std.c.pthread_cond_t = std.c.PTHREAD_COND_INITIALIZER, | |
| 131 | numLocks: c_int = 0, | |
| 132 | writerThreadId: c_int = 0, | |
| 133 | pendingReaders: c_int = 0, | |
| 134 | pendingWriters: c_int = 0, | |
| 135 | attr: i32 = 0, | |
| 136 | __reserved: [12]u8 = [_]u8{0} ** 2, | |
| 137 | }, | |
| 138 | 8 => extern struct { | |
| 139 | numLocks: c_int = 0, | |
| 140 | writerThreadId: c_int = 0, | |
| 141 | pendingReaders: c_int = 0, | |
| 142 | pendingWriters: c_int = 0, | |
| 143 | attr: i32 = 0, | |
| 144 | __reserved: [36]u8 = [_]u8{0} ** 36, | |
| 145 | }, | |
| 146 | else => unreachable, | |
| 147 | }, | |
| 148 | else => extern struct { | |
| 149 | size: [56]u8 align(@alignOf(usize)) = [_]u8{0} ** 56, | |
| 150 | }, | |
| 151 | }; | |
| 126 | 152 | pub const sem_t = extern struct { |
| 127 | 153 | __size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)), |
| 128 | 154 | }; |
lib/std/c/netbsd.zig+16| ... | ... | @@ -54,6 +54,22 @@ pub const pthread_cond_t = extern struct { |
| 54 | 54 | ptc_private: ?*c_void = null, |
| 55 | 55 | }; |
| 56 | 56 | |
| 57 | pub const pthread_rwlock_t = extern struct { | |
| 58 | ptr_magic: c_uint = 0x99990009, | |
| 59 | ptr_interlock: switch (std.builtin.arch) { | |
| 60 | .aarch64, .sparc, .x86_64, .i386 => u8, | |
| 61 | .arm, .powerpc => c_int, | |
| 62 | else => unreachable, | |
| 63 | } = 0, | |
| 64 | ptr_rblocked_first: ?*u8 = null, | |
| 65 | ptr_rblocked_last: ?*u8 = null, | |
| 66 | ptr_wblocked_first: ?*u8 = null, | |
| 67 | ptr_wblocked_last: ?*u8 = null, | |
| 68 | ptr_nreaders: c_uint = 0, | |
| 69 | ptr_owner: std.c.pthread_t = null, | |
| 70 | ptr_private: ?*c_void = null, | |
| 71 | }; | |
| 72 | ||
| 57 | 73 | const pthread_spin_t = switch (builtin.arch) { |
| 58 | 74 | .aarch64, .aarch64_be, .aarch64_32 => u8, |
| 59 | 75 | .mips, .mipsel, .mips64, .mips64el => u32, |
lib/std/c/openbsd.zig+3| ... | ... | @@ -27,6 +27,9 @@ pub const pthread_mutex_t = extern struct { |
| 27 | 27 | pub const pthread_cond_t = extern struct { |
| 28 | 28 | inner: ?*c_void = null, |
| 29 | 29 | }; |
| 30 | pub const pthread_rwlock_t = extern struct { | |
| 31 | ptr: ?*c_void = null, | |
| 32 | }; | |
| 30 | 33 | pub const pthread_spinlock_t = extern struct { |
| 31 | 34 | inner: ?*c_void = null, |
| 32 | 35 | }; |
lib/std/debug.zig+4-4| ... | ... | @@ -50,7 +50,7 @@ pub const LineInfo = struct { |
| 50 | 50 | } |
| 51 | 51 | }; |
| 52 | 52 | |
| 53 | var stderr_mutex = std.Mutex{}; | |
| 53 | var stderr_mutex = std.Thread.Mutex{}; | |
| 54 | 54 | |
| 55 | 55 | /// Deprecated. Use `std.log` functions for logging or `std.debug.print` for |
| 56 | 56 | /// "printf debugging". |
| ... | ... | @@ -65,7 +65,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 65 | 65 | nosuspend stderr.print(fmt, args) catch return; |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | pub fn getStderrMutex() *std.Mutex { | |
| 68 | pub fn getStderrMutex() *std.Thread.Mutex { | |
| 69 | 69 | return &stderr_mutex; |
| 70 | 70 | } |
| 71 | 71 | |
| ... | ... | @@ -235,7 +235,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn { |
| 235 | 235 | var panicking: u8 = 0; |
| 236 | 236 | |
| 237 | 237 | // Locked to avoid interleaving panic messages from multiple threads. |
| 238 | var panic_mutex = std.Mutex{}; | |
| 238 | var panic_mutex = std.Thread.Mutex{}; | |
| 239 | 239 | |
| 240 | 240 | /// Counts how many times the panic handler is invoked by this thread. |
| 241 | 241 | /// This is used to catch and handle panics triggered by the panic handler. |
| ... | ... | @@ -280,7 +280,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 280 | 280 | // and call abort() |
| 281 | 281 | |
| 282 | 282 | // Sleep forever without hammering the CPU |
| 283 | var event: std.StaticResetEvent = .{}; | |
| 283 | var event: std.Thread.StaticResetEvent = .{}; | |
| 284 | 284 | event.wait(); |
| 285 | 285 | unreachable; |
| 286 | 286 | } |
lib/std/event/lock.zig+1-1| ... | ... | @@ -16,7 +16,7 @@ const Loop = std.event.Loop; |
| 16 | 16 | /// Allows only one actor to hold the lock. |
| 17 | 17 | /// TODO: make this API also work in blocking I/O mode. |
| 18 | 18 | pub const Lock = struct { |
| 19 | mutex: std.Mutex = std.Mutex{}, | |
| 19 | mutex: std.Thread.Mutex = std.Thread.Mutex{}, | |
| 20 | 20 | head: usize = UNLOCKED, |
| 21 | 21 | |
| 22 | 22 | const UNLOCKED = 0; |
lib/std/event/loop.zig+3-3| ... | ... | @@ -29,7 +29,7 @@ pub const Loop = struct { |
| 29 | 29 | fs_thread: *Thread, |
| 30 | 30 | fs_queue: std.atomic.Queue(Request), |
| 31 | 31 | fs_end_request: Request.Node, |
| 32 | fs_thread_wakeup: std.ResetEvent, | |
| 32 | fs_thread_wakeup: std.Thread.ResetEvent, | |
| 33 | 33 | |
| 34 | 34 | /// For resources that have the same lifetime as the `Loop`. |
| 35 | 35 | /// This is only used by `Loop` for the thread pool and associated resources. |
| ... | ... | @@ -785,7 +785,7 @@ pub const Loop = struct { |
| 785 | 785 | timer: std.time.Timer, |
| 786 | 786 | waiters: Waiters, |
| 787 | 787 | thread: *std.Thread, |
| 788 | event: std.AutoResetEvent, | |
| 788 | event: std.Thread.AutoResetEvent, | |
| 789 | 789 | is_running: bool, |
| 790 | 790 | |
| 791 | 791 | /// Initialize the delay queue by spawning the timer thread |
| ... | ... | @@ -796,7 +796,7 @@ pub const Loop = struct { |
| 796 | 796 | .waiters = DelayQueue.Waiters{ |
| 797 | 797 | .entries = std.atomic.Queue(anyframe).init(), |
| 798 | 798 | }, |
| 799 | .event = std.AutoResetEvent{}, | |
| 799 | .event = std.Thread.AutoResetEvent{}, | |
| 800 | 800 | .is_running = true, |
| 801 | 801 | // Must be last so that it can read the other state, such as `is_running`. |
| 802 | 802 | .thread = try std.Thread.spawn(self, DelayQueue.run), |
lib/std/event/wait_group.zig+1-1| ... | ... | @@ -30,7 +30,7 @@ pub fn WaitGroupGeneric(comptime counter_size: u16) type { |
| 30 | 30 | return struct { |
| 31 | 31 | counter: CounterType = 0, |
| 32 | 32 | max_counter: CounterType = std.math.maxInt(CounterType), |
| 33 | mutex: std.Mutex = .{}, | |
| 33 | mutex: std.Thread.Mutex = .{}, | |
| 34 | 34 | waiters: ?*Waiter = null, |
| 35 | 35 | const Waiter = struct { |
| 36 | 36 | next: ?*Waiter, |
lib/std/fs/test.zig+2-2| ... | ... | @@ -750,7 +750,7 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 750 | 750 | errdefer file.close(); |
| 751 | 751 | |
| 752 | 752 | const S = struct { |
| 753 | const C = struct { dir: *fs.Dir, evt: *std.ResetEvent }; | |
| 753 | const C = struct { dir: *fs.Dir, evt: *std.Thread.ResetEvent }; | |
| 754 | 754 | fn checkFn(ctx: C) !void { |
| 755 | 755 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive }); |
| 756 | 756 | defer file1.close(); |
| ... | ... | @@ -758,7 +758,7 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 758 | 758 | } |
| 759 | 759 | }; |
| 760 | 760 | |
| 761 | var evt: std.ResetEvent = undefined; | |
| 761 | var evt: std.Thread.ResetEvent = undefined; | |
| 762 | 762 | try evt.init(); |
| 763 | 763 | defer evt.deinit(); |
| 764 | 764 |
lib/std/heap/general_purpose_allocator.zig+8-8| ... | ... | @@ -149,13 +149,13 @@ pub const Config = struct { |
| 149 | 149 | thread_safe: bool = !std.builtin.single_threaded, |
| 150 | 150 | |
| 151 | 151 | /// What type of mutex you'd like to use, for thread safety. |
| 152 | /// when specfied, the mutex type must have the same shape as `std.Mutex` and | |
| 153 | /// `std.mutex.Dummy`, and have no required fields. Specifying this field causes | |
| 152 | /// when specfied, the mutex type must have the same shape as `std.Thread.Mutex` and | |
| 153 | /// `std.Thread.Mutex.Dummy`, and have no required fields. Specifying this field causes | |
| 154 | 154 | /// the `thread_safe` field to be ignored. |
| 155 | 155 | /// |
| 156 | 156 | /// when null (default): |
| 157 | /// * the mutex type defaults to `std.Mutex` when thread_safe is enabled. | |
| 158 | /// * the mutex type defaults to `std.mutex.Dummy` otherwise. | |
| 157 | /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled. | |
| 158 | /// * the mutex type defaults to `std.Thread.Mutex.Dummy` otherwise. | |
| 159 | 159 | MutexType: ?type = null, |
| 160 | 160 | |
| 161 | 161 | /// This is a temporary debugging trick you can use to turn segfaults into more helpful |
| ... | ... | @@ -187,9 +187,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 187 | 187 | const mutex_init = if (config.MutexType) |T| |
| 188 | 188 | T{} |
| 189 | 189 | else if (config.thread_safe) |
| 190 | std.Mutex{} | |
| 190 | std.Thread.Mutex{} | |
| 191 | 191 | else |
| 192 | std.mutex.Dummy{}; | |
| 192 | std.Thread.Mutex.Dummy{}; | |
| 193 | 193 | |
| 194 | 194 | const stack_n = config.stack_trace_frames; |
| 195 | 195 | const one_trace_size = @sizeOf(usize) * stack_n; |
| ... | ... | @@ -869,9 +869,9 @@ test "realloc large object to small object" { |
| 869 | 869 | } |
| 870 | 870 | |
| 871 | 871 | test "overrideable mutexes" { |
| 872 | var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Mutex }){ | |
| 872 | var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Thread.Mutex }){ | |
| 873 | 873 | .backing_allocator = std.testing.allocator, |
| 874 | .mutex = std.Mutex{}, | |
| 874 | .mutex = std.Thread.Mutex{}, | |
| 875 | 875 | }; |
| 876 | 876 | defer std.testing.expect(!gpa.deinit()); |
| 877 | 877 | const allocator = &gpa.allocator; |
lib/std/mutex.zig deleted-379| ... | ... | @@ -1,379 +0,0 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 os = std.os; | |
| 9 | const assert = std.debug.assert; | |
| 10 | const windows = os.windows; | |
| 11 | const testing = std.testing; | |
| 12 | const SpinLock = std.SpinLock; | |
| 13 | const StaticResetEvent = std.StaticResetEvent; | |
| 14 | ||
| 15 | /// Lock may be held only once. If the same thread tries to acquire | |
| 16 | /// the same mutex twice, it deadlocks. This type supports static | |
| 17 | /// initialization and is at most `@sizeOf(usize)` in size. When an | |
| 18 | /// application is built in single threaded release mode, all the | |
| 19 | /// functions are no-ops. In single threaded debug mode, there is | |
| 20 | /// deadlock detection. | |
| 21 | /// | |
| 22 | /// Example usage: | |
| 23 | /// var m = Mutex{}; | |
| 24 | /// | |
| 25 | /// const lock = m.acquire(); | |
| 26 | /// defer lock.release(); | |
| 27 | /// ... critical code | |
| 28 | /// | |
| 29 | /// Non-blocking: | |
| 30 | /// if (m.tryAcquire) |lock| { | |
| 31 | /// defer lock.release(); | |
| 32 | /// // ... critical section | |
| 33 | /// } else { | |
| 34 | /// // ... lock not acquired | |
| 35 | /// } | |
| 36 | pub const Mutex = if (builtin.single_threaded) | |
| 37 | Dummy | |
| 38 | else if (builtin.os.tag == .windows) | |
| 39 | WindowsMutex | |
| 40 | else if (std.Thread.use_pthreads) | |
| 41 | PthreadMutex | |
| 42 | else if (builtin.link_libc or builtin.os.tag == .linux) | |
| 43 | // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs | |
| 44 | struct { | |
| 45 | state: usize = 0, | |
| 46 | ||
| 47 | /// number of times to spin trying to acquire the lock. | |
| 48 | /// https://webkit.org/blog/6161/locking-in-webkit/ | |
| 49 | const SPIN_COUNT = 40; | |
| 50 | ||
| 51 | const MUTEX_LOCK: usize = 1 << 0; | |
| 52 | const QUEUE_LOCK: usize = 1 << 1; | |
| 53 | const QUEUE_MASK: usize = ~(MUTEX_LOCK | QUEUE_LOCK); | |
| 54 | ||
| 55 | const Node = struct { | |
| 56 | next: ?*Node, | |
| 57 | event: StaticResetEvent, | |
| 58 | }; | |
| 59 | ||
| 60 | pub fn tryAcquire(self: *Mutex) ?Held { | |
| 61 | if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null) | |
| 62 | return null; | |
| 63 | return Held{ .mutex = self }; | |
| 64 | } | |
| 65 | ||
| 66 | pub fn acquire(self: *Mutex) Held { | |
| 67 | return self.tryAcquire() orelse { | |
| 68 | self.acquireSlow(); | |
| 69 | return Held{ .mutex = self }; | |
| 70 | }; | |
| 71 | } | |
| 72 | ||
| 73 | fn acquireSlow(self: *Mutex) void { | |
| 74 | // inlining the fast path and hiding *Slow() | |
| 75 | // calls behind a @setCold(true) appears to | |
| 76 | // improve performance in release builds. | |
| 77 | @setCold(true); | |
| 78 | while (true) { | |
| 79 | ||
| 80 | // try and spin for a bit to acquire the mutex if theres currently no queue | |
| 81 | var spin_count: u32 = SPIN_COUNT; | |
| 82 | var state = @atomicLoad(usize, &self.state, .Monotonic); | |
| 83 | while (spin_count != 0) : (spin_count -= 1) { | |
| 84 | if (state & MUTEX_LOCK == 0) { | |
| 85 | _ = @cmpxchgWeak(usize, &self.state, state, state | MUTEX_LOCK, .Acquire, .Monotonic) orelse return; | |
| 86 | } else if (state & QUEUE_MASK == 0) { | |
| 87 | break; | |
| 88 | } | |
| 89 | SpinLock.yield(); | |
| 90 | state = @atomicLoad(usize, &self.state, .Monotonic); | |
| 91 | } | |
| 92 | ||
| 93 | // create the StaticResetEvent node on the stack | |
| 94 | // (faster than threadlocal on platforms like OSX) | |
| 95 | var node: Node = .{ | |
| 96 | .next = undefined, | |
| 97 | .event = .{}, | |
| 98 | }; | |
| 99 | ||
| 100 | // we've spun too long, try and add our node to the LIFO queue. | |
| 101 | // if the mutex becomes available in the process, try and grab it instead. | |
| 102 | while (true) { | |
| 103 | if (state & MUTEX_LOCK == 0) { | |
| 104 | _ = @cmpxchgWeak(usize, &self.state, state, state | MUTEX_LOCK, .Acquire, .Monotonic) orelse return; | |
| 105 | } else { | |
| 106 | node.next = @intToPtr(?*Node, state & QUEUE_MASK); | |
| 107 | const new_state = @ptrToInt(&node) | (state & ~QUEUE_MASK); | |
| 108 | _ = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Monotonic) orelse { | |
| 109 | node.event.wait(); | |
| 110 | break; | |
| 111 | }; | |
| 112 | } | |
| 113 | SpinLock.yield(); | |
| 114 | state = @atomicLoad(usize, &self.state, .Monotonic); | |
| 115 | } | |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | /// Returned when the lock is acquired. Call release to | |
| 120 | /// release. | |
| 121 | pub const Held = struct { | |
| 122 | mutex: *Mutex, | |
| 123 | ||
| 124 | /// Release the held lock. | |
| 125 | pub fn release(self: Held) void { | |
| 126 | // first, remove the lock bit so another possibly parallel acquire() can succeed. | |
| 127 | // use .Sub since it can be usually compiled down more efficiency | |
| 128 | // (`lock sub` on x86) vs .And ~MUTEX_LOCK (`lock cmpxchg` loop on x86) | |
| 129 | const state = @atomicRmw(usize, &self.mutex.state, .Sub, MUTEX_LOCK, .Release); | |
| 130 | ||
| 131 | // if the LIFO queue isnt locked and it has a node, try and wake up the node. | |
| 132 | if ((state & QUEUE_LOCK) == 0 and (state & QUEUE_MASK) != 0) | |
| 133 | self.mutex.releaseSlow(); | |
| 134 | } | |
| 135 | }; | |
| 136 | ||
| 137 | fn releaseSlow(self: *Mutex) void { | |
| 138 | @setCold(true); | |
| 139 | ||
| 140 | // try and lock the LFIO queue to pop a node off, | |
| 141 | // stopping altogether if its already locked or the queue is empty | |
| 142 | var state = @atomicLoad(usize, &self.state, .Monotonic); | |
| 143 | while (true) : (SpinLock.loopHint(1)) { | |
| 144 | if (state & QUEUE_LOCK != 0 or state & QUEUE_MASK == 0) | |
| 145 | return; | |
| 146 | state = @cmpxchgWeak(usize, &self.state, state, state | QUEUE_LOCK, .Acquire, .Monotonic) orelse break; | |
| 147 | } | |
| 148 | ||
| 149 | // acquired the QUEUE_LOCK, try and pop a node to wake it. | |
| 150 | // if the mutex is locked, then unset QUEUE_LOCK and let | |
| 151 | // the thread who holds the mutex do the wake-up on unlock() | |
| 152 | while (true) : (SpinLock.loopHint(1)) { | |
| 153 | if ((state & MUTEX_LOCK) != 0) { | |
| 154 | state = @cmpxchgWeak(usize, &self.state, state, state & ~QUEUE_LOCK, .Release, .Acquire) orelse return; | |
| 155 | } else { | |
| 156 | const node = @intToPtr(*Node, state & QUEUE_MASK); | |
| 157 | const new_state = @ptrToInt(node.next); | |
| 158 | state = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Acquire) orelse { | |
| 159 | node.event.set(); | |
| 160 | return; | |
| 161 | }; | |
| 162 | } | |
| 163 | } | |
| 164 | } | |
| 165 | } | |
| 166 | ||
| 167 | // for platforms without a known OS blocking | |
| 168 | // primitive, default to SpinLock for correctness | |
| 169 | else | |
| 170 | SpinLock; | |
| 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 | ||
| 218 | /// This has the sematics as `Mutex`, however it does not actually do any | |
| 219 | /// synchronization. Operations are safety-checked no-ops. | |
| 220 | pub const Dummy = struct { | |
| 221 | lock: @TypeOf(lock_init) = lock_init, | |
| 222 | ||
| 223 | const lock_init = if (std.debug.runtime_safety) false else {}; | |
| 224 | ||
| 225 | pub const Held = struct { | |
| 226 | mutex: *Dummy, | |
| 227 | ||
| 228 | pub fn release(self: Held) void { | |
| 229 | if (std.debug.runtime_safety) { | |
| 230 | self.mutex.lock = false; | |
| 231 | } | |
| 232 | } | |
| 233 | }; | |
| 234 | ||
| 235 | /// Create a new mutex in unlocked state. | |
| 236 | pub const init = Dummy{}; | |
| 237 | ||
| 238 | /// Try to acquire the mutex without blocking. Returns null if | |
| 239 | /// the mutex is unavailable. Otherwise returns Held. Call | |
| 240 | /// release on Held. | |
| 241 | pub fn tryAcquire(self: *Dummy) ?Held { | |
| 242 | if (std.debug.runtime_safety) { | |
| 243 | if (self.lock) return null; | |
| 244 | self.lock = true; | |
| 245 | } | |
| 246 | return Held{ .mutex = self }; | |
| 247 | } | |
| 248 | ||
| 249 | /// Acquire the mutex. Will deadlock if the mutex is already | |
| 250 | /// held by the calling thread. | |
| 251 | pub fn acquire(self: *Dummy) Held { | |
| 252 | return self.tryAcquire() orelse @panic("deadlock detected"); | |
| 253 | } | |
| 254 | }; | |
| 255 | ||
| 256 | // https://locklessinc.com/articles/keyed_events/ | |
| 257 | const WindowsMutex = struct { | |
| 258 | state: State = State{ .waiters = 0 }, | |
| 259 | ||
| 260 | const State = extern union { | |
| 261 | locked: u8, | |
| 262 | waiters: u32, | |
| 263 | }; | |
| 264 | ||
| 265 | const WAKE = 1 << 8; | |
| 266 | const WAIT = 1 << 9; | |
| 267 | ||
| 268 | pub fn tryAcquire(self: *WindowsMutex) ?Held { | |
| 269 | if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) != 0) | |
| 270 | return null; | |
| 271 | return Held{ .mutex = self }; | |
| 272 | } | |
| 273 | ||
| 274 | pub fn acquire(self: *WindowsMutex) Held { | |
| 275 | return self.tryAcquire() orelse self.acquireSlow(); | |
| 276 | } | |
| 277 | ||
| 278 | fn acquireSpinning(self: *WindowsMutex) Held { | |
| 279 | @setCold(true); | |
| 280 | while (true) : (SpinLock.yield()) { | |
| 281 | return self.tryAcquire() orelse continue; | |
| 282 | } | |
| 283 | } | |
| 284 | ||
| 285 | fn acquireSlow(self: *WindowsMutex) Held { | |
| 286 | // try to use NT keyed events for blocking, falling back to spinlock if unavailable | |
| 287 | @setCold(true); | |
| 288 | const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return self.acquireSpinning(); | |
| 289 | const key = @ptrCast(*const c_void, &self.state.waiters); | |
| 290 | ||
| 291 | while (true) : (SpinLock.loopHint(1)) { | |
| 292 | const waiters = @atomicLoad(u32, &self.state.waiters, .Monotonic); | |
| 293 | ||
| 294 | // try and take lock if unlocked | |
| 295 | if ((waiters & 1) == 0) { | |
| 296 | if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) == 0) { | |
| 297 | return Held{ .mutex = self }; | |
| 298 | } | |
| 299 | ||
| 300 | // otherwise, try and update the waiting count. | |
| 301 | // then unset the WAKE bit so that another unlocker can wake up a thread. | |
| 302 | } else if (@cmpxchgWeak(u32, &self.state.waiters, waiters, (waiters + WAIT) | 1, .Monotonic, .Monotonic) == null) { | |
| 303 | const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); | |
| 304 | assert(rc == .SUCCESS); | |
| 305 | _ = @atomicRmw(u32, &self.state.waiters, .Sub, WAKE, .Monotonic); | |
| 306 | } | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | pub const Held = struct { | |
| 311 | mutex: *WindowsMutex, | |
| 312 | ||
| 313 | pub fn release(self: Held) void { | |
| 314 | // unlock without a rmw/cmpxchg instruction | |
| 315 | @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release); | |
| 316 | const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return; | |
| 317 | const key = @ptrCast(*const c_void, &self.mutex.state.waiters); | |
| 318 | ||
| 319 | while (true) : (SpinLock.loopHint(1)) { | |
| 320 | const waiters = @atomicLoad(u32, &self.mutex.state.waiters, .Monotonic); | |
| 321 | ||
| 322 | // no one is waiting | |
| 323 | if (waiters < WAIT) return; | |
| 324 | // someone grabbed the lock and will do the wake instead | |
| 325 | if (waiters & 1 != 0) return; | |
| 326 | // someone else is currently waking up | |
| 327 | if (waiters & WAKE != 0) return; | |
| 328 | ||
| 329 | // try to decrease the waiter count & set the WAKE bit meaning a thread is waking up | |
| 330 | if (@cmpxchgWeak(u32, &self.mutex.state.waiters, waiters, waiters - WAIT + WAKE, .Release, .Monotonic) == null) { | |
| 331 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); | |
| 332 | assert(rc == .SUCCESS); | |
| 333 | return; | |
| 334 | } | |
| 335 | } | |
| 336 | } | |
| 337 | }; | |
| 338 | }; | |
| 339 | ||
| 340 | const TestContext = struct { | |
| 341 | mutex: *Mutex, | |
| 342 | data: i128, | |
| 343 | ||
| 344 | const incr_count = 10000; | |
| 345 | }; | |
| 346 | ||
| 347 | test "std.Mutex" { | |
| 348 | var mutex = Mutex{}; | |
| 349 | ||
| 350 | var context = TestContext{ | |
| 351 | .mutex = &mutex, | |
| 352 | .data = 0, | |
| 353 | }; | |
| 354 | ||
| 355 | if (builtin.single_threaded) { | |
| 356 | worker(&context); | |
| 357 | testing.expect(context.data == TestContext.incr_count); | |
| 358 | } else { | |
| 359 | const thread_count = 10; | |
| 360 | var threads: [thread_count]*std.Thread = undefined; | |
| 361 | for (threads) |*t| { | |
| 362 | t.* = try std.Thread.spawn(&context, worker); | |
| 363 | } | |
| 364 | for (threads) |t| | |
| 365 | t.wait(); | |
| 366 | ||
| 367 | testing.expect(context.data == thread_count * TestContext.incr_count); | |
| 368 | } | |
| 369 | } | |
| 370 | ||
| 371 | fn worker(ctx: *TestContext) void { | |
| 372 | var i: usize = 0; | |
| 373 | while (i != TestContext.incr_count) : (i += 1) { | |
| 374 | const held = ctx.mutex.acquire(); | |
| 375 | defer held.release(); | |
| 376 | ||
| 377 | ctx.data += 1; | |
| 378 | } | |
| 379 | } |
lib/std/once.zig+1-1| ... | ... | @@ -15,7 +15,7 @@ pub fn once(comptime f: fn () void) Once(f) { |
| 15 | 15 | pub fn Once(comptime f: fn () void) type { |
| 16 | 16 | return struct { |
| 17 | 17 | done: bool = false, |
| 18 | mutex: std.Mutex = std.Mutex{}, | |
| 18 | mutex: std.Thread.Mutex = std.Thread.Mutex{}, | |
| 19 | 19 | |
| 20 | 20 | /// Call the function `f`. |
| 21 | 21 | /// If `call` is invoked multiple times `f` will be executed only the |
lib/std/os/windows/bits.zig+5| ... | ... | @@ -1635,3 +1635,8 @@ pub const OBJECT_NAME_INFORMATION = extern struct { |
| 1635 | 1635 | Name: UNICODE_STRING, |
| 1636 | 1636 | }; |
| 1637 | 1637 | pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION; |
| 1638 | ||
| 1639 | pub const SRWLOCK = usize; | |
| 1640 | pub const SRWLOCK_INIT: SRWLOCK = 0; | |
| 1641 | pub const CONDITION_VARIABLE = usize; | |
| 1642 | pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = 0; |
lib/std/os/windows/kernel32.zig+13| ... | ... | @@ -289,3 +289,16 @@ pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWO |
| 289 | 289 | pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(WINAPI) BOOL; |
| 290 | 290 | |
| 291 | 291 | pub extern "kernel32" fn FlushFileBuffers(hFile: HANDLE) callconv(WINAPI) BOOL; |
| 292 | ||
| 293 | pub extern "kernel32" fn WakeAllConditionVariable(c: *CONDITION_VARIABLE) callconv(WINAPI) void; | |
| 294 | pub extern "kernel32" fn WakeConditionVariable(c: *CONDITION_VARIABLE) callconv(WINAPI) void; | |
| 295 | pub extern "kernel32" fn SleepConditionVariableSRW( | |
| 296 | c: *CONDITION_VARIABLE, | |
| 297 | s: *SRWLOCK, | |
| 298 | t: DWORD, | |
| 299 | f: ULONG, | |
| 300 | ) callconv(WINAPI) BOOL; | |
| 301 | ||
| 302 | pub extern "kernel32" fn TryAcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) BOOL; | |
| 303 | pub extern "kernel32" fn AcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void; | |
| 304 | pub extern "kernel32" fn ReleaseSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void; |
lib/std/std.zig+1-12| ... | ... | @@ -13,7 +13,6 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; |
| 13 | 13 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; |
| 14 | 14 | pub const AutoHashMap = hash_map.AutoHashMap; |
| 15 | 15 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; |
| 16 | pub const AutoResetEvent = @import("auto_reset_event.zig").AutoResetEvent; | |
| 17 | 16 | pub const BufMap = @import("buf_map.zig").BufMap; |
| 18 | 17 | pub const BufSet = @import("buf_set.zig").BufSet; |
| 19 | 18 | pub const ChildProcess = @import("child_process.zig").ChildProcess; |
| ... | ... | @@ -21,26 +20,21 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM |
| 21 | 20 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 22 | 21 | pub const HashMap = hash_map.HashMap; |
| 23 | 22 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; |
| 24 | pub const mutex = @import("mutex.zig"); | |
| 25 | pub const Mutex = mutex.Mutex; | |
| 26 | 23 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; |
| 27 | 24 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; |
| 28 | 25 | pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; |
| 29 | 26 | pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; |
| 30 | 27 | pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; |
| 31 | 28 | pub const Progress = @import("Progress.zig"); |
| 32 | pub const ResetEvent = @import("ResetEvent.zig"); | |
| 33 | 29 | pub const SemanticVersion = @import("SemanticVersion.zig"); |
| 34 | 30 | pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; |
| 35 | pub const SpinLock = @import("SpinLock.zig"); | |
| 36 | pub const StaticResetEvent = @import("StaticResetEvent.zig"); | |
| 37 | 31 | pub const StringHashMap = hash_map.StringHashMap; |
| 38 | 32 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; |
| 39 | 33 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; |
| 40 | 34 | pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged; |
| 41 | 35 | pub const TailQueue = @import("linked_list.zig").TailQueue; |
| 42 | 36 | pub const Target = @import("target.zig").Target; |
| 43 | pub const Thread = @import("thread.zig").Thread; | |
| 37 | pub const Thread = @import("Thread.zig"); | |
| 44 | 38 | |
| 45 | 39 | pub const array_hash_map = @import("array_hash_map.zig"); |
| 46 | 40 | pub const atomic = @import("atomic.zig"); |
| ... | ... | @@ -98,12 +92,7 @@ test "" { |
| 98 | 92 | // server is hitting OOM. TODO revert this after stage2 arrives. |
| 99 | 93 | _ = ChildProcess; |
| 100 | 94 | _ = DynLib; |
| 101 | _ = mutex; | |
| 102 | _ = Mutex; | |
| 103 | 95 | _ = Progress; |
| 104 | _ = ResetEvent; | |
| 105 | _ = SpinLock; | |
| 106 | _ = StaticResetEvent; | |
| 107 | 96 | _ = Target; |
| 108 | 97 | _ = Thread; |
| 109 | 98 |
lib/std/thread.zig deleted-526| ... | ... | @@ -1,526 +0,0 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 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 = std.builtin; | |
| 8 | const os = std.os; | |
| 9 | const mem = std.mem; | |
| 10 | const windows = std.os.windows; | |
| 11 | const c = std.c; | |
| 12 | const assert = std.debug.assert; | |
| 13 | ||
| 14 | const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"; | |
| 15 | ||
| 16 | pub const Thread = struct { | |
| 17 | data: Data, | |
| 18 | ||
| 19 | pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc; | |
| 20 | ||
| 21 | /// Represents a kernel thread handle. | |
| 22 | /// May be an integer or a pointer depending on the platform. | |
| 23 | /// On Linux and POSIX, this is the same as Id. | |
| 24 | pub const Handle = if (use_pthreads) | |
| 25 | c.pthread_t | |
| 26 | else switch (std.Target.current.os.tag) { | |
| 27 | .linux => i32, | |
| 28 | .windows => windows.HANDLE, | |
| 29 | else => void, | |
| 30 | }; | |
| 31 | ||
| 32 | /// Represents a unique ID per thread. | |
| 33 | /// May be an integer or pointer depending on the platform. | |
| 34 | /// On Linux and POSIX, this is the same as Handle. | |
| 35 | pub const Id = switch (std.Target.current.os.tag) { | |
| 36 | .windows => windows.DWORD, | |
| 37 | else => Handle, | |
| 38 | }; | |
| 39 | ||
| 40 | pub const Data = if (use_pthreads) | |
| 41 | struct { | |
| 42 | handle: Thread.Handle, | |
| 43 | memory: []u8, | |
| 44 | } | |
| 45 | else switch (std.Target.current.os.tag) { | |
| 46 | .linux => struct { | |
| 47 | handle: Thread.Handle, | |
| 48 | memory: []align(mem.page_size) u8, | |
| 49 | }, | |
| 50 | .windows => struct { | |
| 51 | handle: Thread.Handle, | |
| 52 | alloc_start: *c_void, | |
| 53 | heap_handle: windows.HANDLE, | |
| 54 | }, | |
| 55 | else => struct {}, | |
| 56 | }; | |
| 57 | ||
| 58 | /// Returns the ID of the calling thread. | |
| 59 | /// Makes a syscall every time the function is called. | |
| 60 | /// On Linux and POSIX, this Id is the same as a Handle. | |
| 61 | pub fn getCurrentId() Id { | |
| 62 | if (use_pthreads) { | |
| 63 | return c.pthread_self(); | |
| 64 | } else | |
| 65 | return switch (std.Target.current.os.tag) { | |
| 66 | .linux => os.linux.gettid(), | |
| 67 | .windows => windows.kernel32.GetCurrentThreadId(), | |
| 68 | else => @compileError("Unsupported OS"), | |
| 69 | }; | |
| 70 | } | |
| 71 | ||
| 72 | /// Returns the handle of this thread. | |
| 73 | /// On Linux and POSIX, this is the same as Id. | |
| 74 | /// On Linux, it is possible that the thread spawned with `spawn` | |
| 75 | /// finishes executing entirely before the clone syscall completes. In this | |
| 76 | /// case, this function will return 0 rather than the no-longer-existing thread's | |
| 77 | /// pid. | |
| 78 | pub fn handle(self: Thread) Handle { | |
| 79 | return self.data.handle; | |
| 80 | } | |
| 81 | ||
| 82 | pub fn wait(self: *Thread) void { | |
| 83 | if (use_pthreads) { | |
| 84 | const err = c.pthread_join(self.data.handle, null); | |
| 85 | switch (err) { | |
| 86 | 0 => {}, | |
| 87 | os.EINVAL => unreachable, | |
| 88 | os.ESRCH => unreachable, | |
| 89 | os.EDEADLK => unreachable, | |
| 90 | else => unreachable, | |
| 91 | } | |
| 92 | std.heap.c_allocator.free(self.data.memory); | |
| 93 | std.heap.c_allocator.destroy(self); | |
| 94 | } else switch (std.Target.current.os.tag) { | |
| 95 | .linux => { | |
| 96 | while (true) { | |
| 97 | const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); | |
| 98 | if (pid_value == 0) break; | |
| 99 | const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null); | |
| 100 | switch (os.linux.getErrno(rc)) { | |
| 101 | 0 => continue, | |
| 102 | os.EINTR => continue, | |
| 103 | os.EAGAIN => continue, | |
| 104 | else => unreachable, | |
| 105 | } | |
| 106 | } | |
| 107 | os.munmap(self.data.memory); | |
| 108 | }, | |
| 109 | .windows => { | |
| 110 | windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable; | |
| 111 | windows.CloseHandle(self.data.handle); | |
| 112 | windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start); | |
| 113 | }, | |
| 114 | else => @compileError("Unsupported OS"), | |
| 115 | } | |
| 116 | } | |
| 117 | ||
| 118 | pub const SpawnError = error{ | |
| 119 | /// A system-imposed limit on the number of threads was encountered. | |
| 120 | /// There are a number of limits that may trigger this error: | |
| 121 | /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), | |
| 122 | /// which limits the number of processes and threads for a real | |
| 123 | /// user ID, was reached; | |
| 124 | /// * the kernel's system-wide limit on the number of processes and | |
| 125 | /// threads, /proc/sys/kernel/threads-max, was reached (see | |
| 126 | /// proc(5)); | |
| 127 | /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was | |
| 128 | /// reached (see proc(5)); or | |
| 129 | /// * the PID limit (pids.max) imposed by the cgroup "process num‐ | |
| 130 | /// ber" (PIDs) controller was reached. | |
| 131 | ThreadQuotaExceeded, | |
| 132 | ||
| 133 | /// The kernel cannot allocate sufficient memory to allocate a task structure | |
| 134 | /// for the child, or to copy those parts of the caller's context that need to | |
| 135 | /// be copied. | |
| 136 | SystemResources, | |
| 137 | ||
| 138 | /// Not enough userland memory to spawn the thread. | |
| 139 | OutOfMemory, | |
| 140 | ||
| 141 | /// `mlockall` is enabled, and the memory needed to spawn the thread | |
| 142 | /// would exceed the limit. | |
| 143 | LockedMemoryLimitExceeded, | |
| 144 | ||
| 145 | Unexpected, | |
| 146 | }; | |
| 147 | ||
| 148 | /// caller must call wait on the returned thread | |
| 149 | /// fn startFn(@TypeOf(context)) T | |
| 150 | /// where T is u8, noreturn, void, or !void | |
| 151 | /// caller must call wait on the returned thread | |
| 152 | pub fn spawn(context: anytype, comptime startFn: anytype) SpawnError!*Thread { | |
| 153 | if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); | |
| 154 | // TODO compile-time call graph analysis to determine stack upper bound | |
| 155 | // https://github.com/ziglang/zig/issues/157 | |
| 156 | const default_stack_size = 16 * 1024 * 1024; | |
| 157 | ||
| 158 | const Context = @TypeOf(context); | |
| 159 | comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context); | |
| 160 | ||
| 161 | if (std.Target.current.os.tag == .windows) { | |
| 162 | const WinThread = struct { | |
| 163 | const OuterContext = struct { | |
| 164 | thread: Thread, | |
| 165 | inner: Context, | |
| 166 | }; | |
| 167 | fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD { | |
| 168 | const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; | |
| 169 | ||
| 170 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | |
| 171 | .NoReturn => { | |
| 172 | startFn(arg); | |
| 173 | }, | |
| 174 | .Void => { | |
| 175 | startFn(arg); | |
| 176 | return 0; | |
| 177 | }, | |
| 178 | .Int => |info| { | |
| 179 | if (info.bits != 8) { | |
| 180 | @compileError(bad_startfn_ret); | |
| 181 | } | |
| 182 | return startFn(arg); | |
| 183 | }, | |
| 184 | .ErrorUnion => |info| { | |
| 185 | if (info.payload != void) { | |
| 186 | @compileError(bad_startfn_ret); | |
| 187 | } | |
| 188 | startFn(arg) catch |err| { | |
| 189 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | |
| 190 | if (@errorReturnTrace()) |trace| { | |
| 191 | std.debug.dumpStackTrace(trace.*); | |
| 192 | } | |
| 193 | }; | |
| 194 | return 0; | |
| 195 | }, | |
| 196 | else => @compileError(bad_startfn_ret), | |
| 197 | } | |
| 198 | } | |
| 199 | }; | |
| 200 | ||
| 201 | const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory; | |
| 202 | const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); | |
| 203 | const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory; | |
| 204 | errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0); | |
| 205 | const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; | |
| 206 | const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; | |
| 207 | outer_context.* = WinThread.OuterContext{ | |
| 208 | .thread = Thread{ | |
| 209 | .data = Thread.Data{ | |
| 210 | .heap_handle = heap_handle, | |
| 211 | .alloc_start = bytes_ptr, | |
| 212 | .handle = undefined, | |
| 213 | }, | |
| 214 | }, | |
| 215 | .inner = context, | |
| 216 | }; | |
| 217 | ||
| 218 | const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); | |
| 219 | outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { | |
| 220 | switch (windows.kernel32.GetLastError()) { | |
| 221 | else => |err| return windows.unexpectedError(err), | |
| 222 | } | |
| 223 | }; | |
| 224 | return &outer_context.thread; | |
| 225 | } | |
| 226 | ||
| 227 | const MainFuncs = struct { | |
| 228 | fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 { | |
| 229 | const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; | |
| 230 | ||
| 231 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | |
| 232 | .NoReturn => { | |
| 233 | startFn(arg); | |
| 234 | }, | |
| 235 | .Void => { | |
| 236 | startFn(arg); | |
| 237 | return 0; | |
| 238 | }, | |
| 239 | .Int => |info| { | |
| 240 | if (info.bits != 8) { | |
| 241 | @compileError(bad_startfn_ret); | |
| 242 | } | |
| 243 | return startFn(arg); | |
| 244 | }, | |
| 245 | .ErrorUnion => |info| { | |
| 246 | if (info.payload != void) { | |
| 247 | @compileError(bad_startfn_ret); | |
| 248 | } | |
| 249 | startFn(arg) catch |err| { | |
| 250 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | |
| 251 | if (@errorReturnTrace()) |trace| { | |
| 252 | std.debug.dumpStackTrace(trace.*); | |
| 253 | } | |
| 254 | }; | |
| 255 | return 0; | |
| 256 | }, | |
| 257 | else => @compileError(bad_startfn_ret), | |
| 258 | } | |
| 259 | } | |
| 260 | fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void { | |
| 261 | const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; | |
| 262 | ||
| 263 | switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { | |
| 264 | .NoReturn => { | |
| 265 | startFn(arg); | |
| 266 | }, | |
| 267 | .Void => { | |
| 268 | startFn(arg); | |
| 269 | return null; | |
| 270 | }, | |
| 271 | .Int => |info| { | |
| 272 | if (info.bits != 8) { | |
| 273 | @compileError(bad_startfn_ret); | |
| 274 | } | |
| 275 | // pthreads don't support exit status, ignore value | |
| 276 | _ = startFn(arg); | |
| 277 | return null; | |
| 278 | }, | |
| 279 | .ErrorUnion => |info| { | |
| 280 | if (info.payload != void) { | |
| 281 | @compileError(bad_startfn_ret); | |
| 282 | } | |
| 283 | startFn(arg) catch |err| { | |
| 284 | std.debug.warn("error: {s}\n", .{@errorName(err)}); | |
| 285 | if (@errorReturnTrace()) |trace| { | |
| 286 | std.debug.dumpStackTrace(trace.*); | |
| 287 | } | |
| 288 | }; | |
| 289 | return null; | |
| 290 | }, | |
| 291 | else => @compileError(bad_startfn_ret), | |
| 292 | } | |
| 293 | } | |
| 294 | }; | |
| 295 | ||
| 296 | if (Thread.use_pthreads) { | |
| 297 | var attr: c.pthread_attr_t = undefined; | |
| 298 | if (c.pthread_attr_init(&attr) != 0) return error.SystemResources; | |
| 299 | defer assert(c.pthread_attr_destroy(&attr) == 0); | |
| 300 | ||
| 301 | const thread_obj = try std.heap.c_allocator.create(Thread); | |
| 302 | errdefer std.heap.c_allocator.destroy(thread_obj); | |
| 303 | if (@sizeOf(Context) > 0) { | |
| 304 | thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced( | |
| 305 | u8, | |
| 306 | @alignOf(Context), | |
| 307 | @sizeOf(Context), | |
| 308 | .at_least, | |
| 309 | ); | |
| 310 | errdefer std.heap.c_allocator.free(thread_obj.data.memory); | |
| 311 | mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context)); | |
| 312 | } else { | |
| 313 | thread_obj.data.memory = @as([*]u8, undefined)[0..0]; | |
| 314 | } | |
| 315 | ||
| 316 | // Use the same set of parameters used by the libc-less impl. | |
| 317 | assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0); | |
| 318 | assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0); | |
| 319 | ||
| 320 | const err = c.pthread_create( | |
| 321 | &thread_obj.data.handle, | |
| 322 | &attr, | |
| 323 | MainFuncs.posixThreadMain, | |
| 324 | thread_obj.data.memory.ptr, | |
| 325 | ); | |
| 326 | switch (err) { | |
| 327 | 0 => return thread_obj, | |
| 328 | os.EAGAIN => return error.SystemResources, | |
| 329 | os.EPERM => unreachable, | |
| 330 | os.EINVAL => unreachable, | |
| 331 | else => return os.unexpectedErrno(@intCast(usize, err)), | |
| 332 | } | |
| 333 | ||
| 334 | return thread_obj; | |
| 335 | } | |
| 336 | ||
| 337 | var guard_end_offset: usize = undefined; | |
| 338 | var stack_end_offset: usize = undefined; | |
| 339 | var thread_start_offset: usize = undefined; | |
| 340 | var context_start_offset: usize = undefined; | |
| 341 | var tls_start_offset: usize = undefined; | |
| 342 | const mmap_len = blk: { | |
| 343 | var l: usize = mem.page_size; | |
| 344 | // Allocate a guard page right after the end of the stack region | |
| 345 | guard_end_offset = l; | |
| 346 | // The stack itself, which grows downwards. | |
| 347 | l = mem.alignForward(l + default_stack_size, mem.page_size); | |
| 348 | stack_end_offset = l; | |
| 349 | // Above the stack, so that it can be in the same mmap call, put the Thread object. | |
| 350 | l = mem.alignForward(l, @alignOf(Thread)); | |
| 351 | thread_start_offset = l; | |
| 352 | l += @sizeOf(Thread); | |
| 353 | // Next, the Context object. | |
| 354 | if (@sizeOf(Context) != 0) { | |
| 355 | l = mem.alignForward(l, @alignOf(Context)); | |
| 356 | context_start_offset = l; | |
| 357 | l += @sizeOf(Context); | |
| 358 | } | |
| 359 | // Finally, the Thread Local Storage, if any. | |
| 360 | l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align); | |
| 361 | tls_start_offset = l; | |
| 362 | l += os.linux.tls.tls_image.alloc_size; | |
| 363 | // Round the size to the page size. | |
| 364 | break :blk mem.alignForward(l, mem.page_size); | |
| 365 | }; | |
| 366 | ||
| 367 | const mmap_slice = mem: { | |
| 368 | // Map the whole stack with no rw permissions to avoid | |
| 369 | // committing the whole region right away | |
| 370 | const mmap_slice = os.mmap( | |
| 371 | null, | |
| 372 | mmap_len, | |
| 373 | os.PROT_NONE, | |
| 374 | os.MAP_PRIVATE | os.MAP_ANONYMOUS, | |
| 375 | -1, | |
| 376 | 0, | |
| 377 | ) catch |err| switch (err) { | |
| 378 | error.MemoryMappingNotSupported => unreachable, | |
| 379 | error.AccessDenied => unreachable, | |
| 380 | error.PermissionDenied => unreachable, | |
| 381 | else => |e| return e, | |
| 382 | }; | |
| 383 | errdefer os.munmap(mmap_slice); | |
| 384 | ||
| 385 | // Map everything but the guard page as rw | |
| 386 | os.mprotect( | |
| 387 | mmap_slice[guard_end_offset..], | |
| 388 | os.PROT_READ | os.PROT_WRITE, | |
| 389 | ) catch |err| switch (err) { | |
| 390 | error.AccessDenied => unreachable, | |
| 391 | else => |e| return e, | |
| 392 | }; | |
| 393 | ||
| 394 | break :mem mmap_slice; | |
| 395 | }; | |
| 396 | ||
| 397 | const mmap_addr = @ptrToInt(mmap_slice.ptr); | |
| 398 | ||
| 399 | const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); | |
| 400 | thread_ptr.data.memory = mmap_slice; | |
| 401 | ||
| 402 | var arg: usize = undefined; | |
| 403 | if (@sizeOf(Context) != 0) { | |
| 404 | arg = mmap_addr + context_start_offset; | |
| 405 | const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); | |
| 406 | context_ptr.* = context; | |
| 407 | } | |
| 408 | ||
| 409 | if (std.Target.current.os.tag == .linux) { | |
| 410 | const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | | |
| 411 | os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM | | |
| 412 | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | | |
| 413 | os.CLONE_DETACHED | os.CLONE_SETTLS; | |
| 414 | // This structure is only needed when targeting i386 | |
| 415 | var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined; | |
| 416 | ||
| 417 | const tls_area = mmap_slice[tls_start_offset..]; | |
| 418 | const tp_value = os.linux.tls.prepareTLS(tls_area); | |
| 419 | ||
| 420 | const newtls = blk: { | |
| 421 | if (std.Target.current.cpu.arch == .i386) { | |
| 422 | user_desc = os.linux.user_desc{ | |
| 423 | .entry_number = os.linux.tls.tls_image.gdt_entry_number, | |
| 424 | .base_addr = tp_value, | |
| 425 | .limit = 0xfffff, | |
| 426 | .seg_32bit = 1, | |
| 427 | .contents = 0, // Data | |
| 428 | .read_exec_only = 0, | |
| 429 | .limit_in_pages = 1, | |
| 430 | .seg_not_present = 0, | |
| 431 | .useable = 1, | |
| 432 | }; | |
| 433 | break :blk @ptrToInt(&user_desc); | |
| 434 | } else { | |
| 435 | break :blk tp_value; | |
| 436 | } | |
| 437 | }; | |
| 438 | ||
| 439 | const rc = os.linux.clone( | |
| 440 | MainFuncs.linuxThreadMain, | |
| 441 | mmap_addr + stack_end_offset, | |
| 442 | flags, | |
| 443 | arg, | |
| 444 | &thread_ptr.data.handle, | |
| 445 | newtls, | |
| 446 | &thread_ptr.data.handle, | |
| 447 | ); | |
| 448 | switch (os.errno(rc)) { | |
| 449 | 0 => return thread_ptr, | |
| 450 | os.EAGAIN => return error.ThreadQuotaExceeded, | |
| 451 | os.EINVAL => unreachable, | |
| 452 | os.ENOMEM => return error.SystemResources, | |
| 453 | os.ENOSPC => unreachable, | |
| 454 | os.EPERM => unreachable, | |
| 455 | os.EUSERS => unreachable, | |
| 456 | else => |err| return os.unexpectedErrno(err), | |
| 457 | } | |
| 458 | } else { | |
| 459 | @compileError("Unsupported OS"); | |
| 460 | } | |
| 461 | } | |
| 462 | ||
| 463 | pub const CpuCountError = error{ | |
| 464 | PermissionDenied, | |
| 465 | SystemResources, | |
| 466 | Unexpected, | |
| 467 | }; | |
| 468 | ||
| 469 | pub fn cpuCount() CpuCountError!usize { | |
| 470 | if (std.Target.current.os.tag == .linux) { | |
| 471 | const cpu_set = try os.sched_getaffinity(0); | |
| 472 | return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast | |
| 473 | } | |
| 474 | if (std.Target.current.os.tag == .windows) { | |
| 475 | return os.windows.peb().NumberOfProcessors; | |
| 476 | } | |
| 477 | if (std.Target.current.os.tag == .openbsd) { | |
| 478 | var count: c_int = undefined; | |
| 479 | var count_size: usize = @sizeOf(c_int); | |
| 480 | const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE }; | |
| 481 | os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { | |
| 482 | error.NameTooLong, error.UnknownName => unreachable, | |
| 483 | else => |e| return e, | |
| 484 | }; | |
| 485 | return @intCast(usize, count); | |
| 486 | } | |
| 487 | var count: c_int = undefined; | |
| 488 | var count_len: usize = @sizeOf(c_int); | |
| 489 | const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; | |
| 490 | os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { | |
| 491 | error.NameTooLong, error.UnknownName => unreachable, | |
| 492 | else => |e| return e, | |
| 493 | }; | |
| 494 | return @intCast(usize, count); | |
| 495 | } | |
| 496 | ||
| 497 | pub fn getCurrentThreadId() u64 { | |
| 498 | switch (std.Target.current.os.tag) { | |
| 499 | .linux => { | |
| 500 | // Use the syscall directly as musl doesn't provide a wrapper. | |
| 501 | return @bitCast(u32, os.linux.gettid()); | |
| 502 | }, | |
| 503 | .windows => { | |
| 504 | return os.windows.kernel32.GetCurrentThreadId(); | |
| 505 | }, | |
| 506 | .macos, .ios, .watchos, .tvos => { | |
| 507 | var thread_id: u64 = undefined; | |
| 508 | // Pass thread=null to get the current thread ID. | |
| 509 | assert(c.pthread_threadid_np(null, &thread_id) == 0); | |
| 510 | return thread_id; | |
| 511 | }, | |
| 512 | .netbsd => { | |
| 513 | return @bitCast(u32, c._lwp_self()); | |
| 514 | }, | |
| 515 | .freebsd => { | |
| 516 | return @bitCast(u32, c.pthread_getthreadid_np()); | |
| 517 | }, | |
| 518 | .openbsd => { | |
| 519 | return @bitCast(u32, c.getthrid()); | |
| 520 | }, | |
| 521 | else => { | |
| 522 | @compileError("getCurrentThreadId not implemented for this platform"); | |
| 523 | }, | |
| 524 | } | |
| 525 | } | |
| 526 | }; |
src/Compilation.zig+1-1| ... | ... | @@ -125,7 +125,7 @@ owned_link_dir: ?std.fs.Dir, |
| 125 | 125 | color: @import("main.zig").Color = .auto, |
| 126 | 126 | |
| 127 | 127 | /// This mutex guards all `Compilation` mutable state. |
| 128 | mutex: std.Mutex = .{}, | |
| 128 | mutex: std.Thread.Mutex = .{}, | |
| 129 | 129 | |
| 130 | 130 | test_filter: ?[]const u8, |
| 131 | 131 | test_name_prefix: ?[]const u8, |
src/ThreadPool.zig+2-2| ... | ... | @@ -6,14 +6,14 @@ |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const ThreadPool = @This(); |
| 8 | 8 | |
| 9 | lock: std.Mutex = .{}, | |
| 9 | lock: std.Thread.Mutex = .{}, | |
| 10 | 10 | is_running: bool = true, |
| 11 | 11 | allocator: *std.mem.Allocator, |
| 12 | 12 | workers: []Worker, |
| 13 | 13 | run_queue: RunQueue = .{}, |
| 14 | 14 | idle_queue: IdleQueue = .{}, |
| 15 | 15 | |
| 16 | const IdleQueue = std.SinglyLinkedList(std.ResetEvent); | |
| 16 | const IdleQueue = std.SinglyLinkedList(std.Thread.ResetEvent); | |
| 17 | 17 | const RunQueue = std.SinglyLinkedList(Runnable); |
| 18 | 18 | const Runnable = struct { |
| 19 | 19 | runFn: fn (*Runnable) void, |
src/WaitGroup.zig+2-2| ... | ... | @@ -6,9 +6,9 @@ |
| 6 | 6 | const std = @import("std"); |
| 7 | 7 | const WaitGroup = @This(); |
| 8 | 8 | |
| 9 | lock: std.Mutex = .{}, | |
| 9 | lock: std.Thread.Mutex = .{}, | |
| 10 | 10 | counter: usize = 0, |
| 11 | event: std.ResetEvent, | |
| 11 | event: std.Thread.ResetEvent, | |
| 12 | 12 | |
| 13 | 13 | pub fn init(self: *WaitGroup) !void { |
| 14 | 14 | self.* = .{ |