| 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,7 +334,6 @@ set(ZIG_STAGE2_SOURCES |
| 334 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/int.zig" | 334 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/int.zig" |
| 335 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig" | 335 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig" |
| 336 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig" | 336 | "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig" |
| 337 | "${CMAKE_SOURCE_DIR}/lib/std/auto_reset_event.zig" | ||
| 338 | "${CMAKE_SOURCE_DIR}/lib/std/base64.zig" | 337 | "${CMAKE_SOURCE_DIR}/lib/std/base64.zig" |
| 339 | "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig" | 338 | "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig" |
| 340 | "${CMAKE_SOURCE_DIR}/lib/std/builtin.zig" | 339 | "${CMAKE_SOURCE_DIR}/lib/std/builtin.zig" |
| ... | @@ -409,7 +408,6 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -409,7 +408,6 @@ set(ZIG_STAGE2_SOURCES |
| 409 | "${CMAKE_SOURCE_DIR}/lib/std/meta.zig" | 408 | "${CMAKE_SOURCE_DIR}/lib/std/meta.zig" |
| 410 | "${CMAKE_SOURCE_DIR}/lib/std/meta/trailer_flags.zig" | 409 | "${CMAKE_SOURCE_DIR}/lib/std/meta/trailer_flags.zig" |
| 411 | "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig" | 410 | "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig" |
| 412 | "${CMAKE_SOURCE_DIR}/lib/std/mutex.zig" | ||
| 413 | "${CMAKE_SOURCE_DIR}/lib/std/os.zig" | 411 | "${CMAKE_SOURCE_DIR}/lib/std/os.zig" |
| 414 | "${CMAKE_SOURCE_DIR}/lib/std/os/bits.zig" | 412 | "${CMAKE_SOURCE_DIR}/lib/std/os/bits.zig" |
| 415 | "${CMAKE_SOURCE_DIR}/lib/std/os/bits/linux.zig" | 413 | "${CMAKE_SOURCE_DIR}/lib/std/os/bits/linux.zig" |
| ... | @@ -426,8 +424,6 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -426,8 +424,6 @@ set(ZIG_STAGE2_SOURCES |
| 426 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" | 424 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" |
| 427 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" | 425 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" |
| 428 | "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig" | 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 | "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig" | 427 | "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig" |
| 432 | "${CMAKE_SOURCE_DIR}/lib/std/process.zig" | 428 | "${CMAKE_SOURCE_DIR}/lib/std/process.zig" |
| 433 | "${CMAKE_SOURCE_DIR}/lib/std/rand.zig" | 429 | "${CMAKE_SOURCE_DIR}/lib/std/rand.zig" |
| ... | @@ -494,7 +490,6 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -494,7 +490,6 @@ set(ZIG_STAGE2_SOURCES |
| 494 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivmodti4.zig" | 490 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivmodti4.zig" |
| 495 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivti3.zig" | 491 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivti3.zig" |
| 496 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/umodti3.zig" | 492 | "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/umodti3.zig" |
| 497 | "${CMAKE_SOURCE_DIR}/lib/std/SpinLock.zig" | ||
| 498 | "${CMAKE_SOURCE_DIR}/lib/std/start.zig" | 493 | "${CMAKE_SOURCE_DIR}/lib/std/start.zig" |
| 499 | "${CMAKE_SOURCE_DIR}/lib/std/std.zig" | 494 | "${CMAKE_SOURCE_DIR}/lib/std/std.zig" |
| 500 | "${CMAKE_SOURCE_DIR}/lib/std/target.zig" | 495 | "${CMAKE_SOURCE_DIR}/lib/std/target.zig" |
| ... | @@ -513,7 +508,11 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -513,7 +508,11 @@ set(ZIG_STAGE2_SOURCES |
| 513 | "${CMAKE_SOURCE_DIR}/lib/std/target/systemz.zig" | 508 | "${CMAKE_SOURCE_DIR}/lib/std/target/systemz.zig" |
| 514 | "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig" | 509 | "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig" |
| 515 | "${CMAKE_SOURCE_DIR}/lib/std/target/x86.zig" | 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 | "${CMAKE_SOURCE_DIR}/lib/std/time.zig" | 516 | "${CMAKE_SOURCE_DIR}/lib/std/time.zig" |
| 518 | "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig" | 517 | "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig" |
| 519 | "${CMAKE_SOURCE_DIR}/lib/std/zig.zig" | 518 | "${CMAKE_SOURCE_DIR}/lib/std/zig.zig" |
lib/std/Progress.zig+1-1| ... | @@ -50,7 +50,7 @@ done: bool = true, | ... | @@ -50,7 +50,7 @@ done: bool = true, |
| 50 | /// Protects the `refresh` function, as well as `node.recently_updated_child`. | 50 | /// Protects the `refresh` function, as well as `node.recently_updated_child`. |
| 51 | /// Without this, callsites would call `Node.end` and then free `Node` memory | 51 | /// Without this, callsites would call `Node.end` and then free `Node` memory |
| 52 | /// while it was still being accessed by the `refresh` function. | 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 | /// Keeps track of how many columns in the terminal have been output, so that | 55 | /// Keeps track of how many columns in the terminal have been output, so that |
| 56 | /// we can move the cursor back later. | 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,7 +16,7 @@ pub fn Queue(comptime T: type) type { |
| 16 | return struct { | 16 | return struct { |
| 17 | head: ?*Node, | 17 | head: ?*Node, |
| 18 | tail: ?*Node, | 18 | tail: ?*Node, |
| 19 | mutex: std.Mutex, | 19 | mutex: std.Thread.Mutex, |
| 20 | 20 | ||
| 21 | pub const Self = @This(); | 21 | pub const Self = @This(); |
| 22 | pub const Node = std.TailQueue(T).Node; | 22 | pub const Node = std.TailQueue(T).Node; |
| ... | @@ -27,7 +27,7 @@ pub fn Queue(comptime T: type) type { | ... | @@ -27,7 +27,7 @@ pub fn Queue(comptime T: type) type { |
| 27 | return Self{ | 27 | return Self{ |
| 28 | .head = null, | 28 | .head = null, |
| 29 | .tail = null, | 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,6 +338,13 @@ pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) c_int; |
| 338 | pub extern "c" fn pthread_cond_broadcast(cond: *pthread_cond_t) c_int; | 338 | pub extern "c" fn pthread_cond_broadcast(cond: *pthread_cond_t) c_int; |
| 339 | pub extern "c" fn pthread_cond_destroy(cond: *pthread_cond_t) c_int; | 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 | pub const pthread_t = *opaque {}; | 348 | pub const pthread_t = *opaque {}; |
| 342 | pub const FILE = opaque {}; | 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,6 +177,10 @@ pub const pthread_cond_t = extern struct { |
| 177 | __sig: c_long = 0x3CB0B1BB, | 177 | __sig: c_long = 0x3CB0B1BB, |
| 178 | __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__, | 178 | __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__, |
| 179 | }; | 179 | }; |
| 180 | pub const pthread_rwlock_t = extern struct { | ||
| 181 | __sig: c_long = 0x2DA8B3B4, | ||
| 182 | __opaque: [192]u8 = [_]u8{0} ** 192, | ||
| 183 | }; | ||
| 180 | pub const sem_t = c_int; | 184 | pub const sem_t = c_int; |
| 181 | const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40; | 185 | const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40; |
| 182 | const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24; | 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,7 +196,7 @@ pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int |
| 192 | pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; | 196 | pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; |
| 193 | 197 | ||
| 194 | // Grand Central Dispatch is exposed by libSystem. | 198 | // Grand Central Dispatch is exposed by libSystem. |
| 195 | pub const dispatch_semaphore_t = *opaque{}; | 199 | pub const dispatch_semaphore_t = *opaque {}; |
| 196 | pub const dispatch_time_t = u64; | 200 | pub const dispatch_time_t = u64; |
| 197 | pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0); | 201 | pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0); |
| 198 | pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0); | 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,5 +9,8 @@ pub const pthread_mutex_t = extern struct { |
| 9 | pub const pthread_cond_t = extern struct { | 9 | pub const pthread_cond_t = extern struct { |
| 10 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, | 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 | const __SIZEOF_PTHREAD_COND_T = 48; | 15 | const __SIZEOF_PTHREAD_COND_T = 48; |
| 13 | const __SIZEOF_PTHREAD_MUTEX_T = 28; | 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,6 +43,9 @@ pub const pthread_mutex_t = extern struct { |
| 43 | pub const pthread_cond_t = extern struct { | 43 | pub const pthread_cond_t = extern struct { |
| 44 | inner: ?*c_void = null, | 44 | inner: ?*c_void = null, |
| 45 | }; | 45 | }; |
| 46 | pub const pthread_rwlock_t = extern struct { | ||
| 47 | ptr: ?*c_void = null, | ||
| 48 | }; | ||
| 46 | 49 | ||
| 47 | pub const pthread_attr_t = extern struct { | 50 | pub const pthread_attr_t = extern struct { |
| 48 | __size: [56]u8, | 51 | __size: [56]u8, |
lib/std/c/fuchsia.zig+3| ... | @@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct { | ... | @@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct { |
| 9 | pub const pthread_cond_t = extern struct { | 9 | pub const pthread_cond_t = extern struct { |
| 10 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, | 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 | const __SIZEOF_PTHREAD_COND_T = 48; | 15 | const __SIZEOF_PTHREAD_COND_T = 48; |
| 13 | const __SIZEOF_PTHREAD_MUTEX_T = 40; | 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,3 +17,12 @@ pub const pthread_cond_t = extern struct { |
| 17 | waiter_count: i32 = 0, | 17 | waiter_count: i32 = 0, |
| 18 | lock: i32 = 0, | 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,3 +9,6 @@ pub const pthread_mutex_t = extern struct { |
| 9 | pub const pthread_cond_t = extern struct { | 9 | pub const pthread_cond_t = extern struct { |
| 10 | inner: usize = ~@as(usize, 0), | 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,6 +123,32 @@ pub const pthread_mutex_t = extern struct { |
| 123 | pub const pthread_cond_t = extern struct { | 123 | pub const pthread_cond_t = extern struct { |
| 124 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, | 124 | size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, |
| 125 | }; | 125 | }; |
| 126 | pub const 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 | pub const sem_t = extern struct { | 152 | pub const sem_t = extern struct { |
| 127 | __size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)), | 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,6 +54,22 @@ pub const pthread_cond_t = extern struct { |
| 54 | ptc_private: ?*c_void = null, | 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 | const pthread_spin_t = switch (builtin.arch) { | 73 | const pthread_spin_t = switch (builtin.arch) { |
| 58 | .aarch64, .aarch64_be, .aarch64_32 => u8, | 74 | .aarch64, .aarch64_be, .aarch64_32 => u8, |
| 59 | .mips, .mipsel, .mips64, .mips64el => u32, | 75 | .mips, .mipsel, .mips64, .mips64el => u32, |
lib/std/c/openbsd.zig+3| ... | @@ -27,6 +27,9 @@ pub const pthread_mutex_t = extern struct { | ... | @@ -27,6 +27,9 @@ pub const pthread_mutex_t = extern struct { |
| 27 | pub const pthread_cond_t = extern struct { | 27 | pub const pthread_cond_t = extern struct { |
| 28 | inner: ?*c_void = null, | 28 | inner: ?*c_void = null, |
| 29 | }; | 29 | }; |
| 30 | pub const pthread_rwlock_t = extern struct { | ||
| 31 | ptr: ?*c_void = null, | ||
| 32 | }; | ||
| 30 | pub const pthread_spinlock_t = extern struct { | 33 | pub const pthread_spinlock_t = extern struct { |
| 31 | inner: ?*c_void = null, | 34 | inner: ?*c_void = null, |
| 32 | }; | 35 | }; |
lib/std/debug.zig+4-4| ... | @@ -50,7 +50,7 @@ pub const LineInfo = struct { | ... | @@ -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 | /// Deprecated. Use `std.log` functions for logging or `std.debug.print` for | 55 | /// Deprecated. Use `std.log` functions for logging or `std.debug.print` for |
| 56 | /// "printf debugging". | 56 | /// "printf debugging". |
| ... | @@ -65,7 +65,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { | ... | @@ -65,7 +65,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 65 | nosuspend stderr.print(fmt, args) catch return; | 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 | return &stderr_mutex; | 69 | return &stderr_mutex; |
| 70 | } | 70 | } |
| 71 | 71 | ||
| ... | @@ -235,7 +235,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn { | ... | @@ -235,7 +235,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn { |
| 235 | var panicking: u8 = 0; | 235 | var panicking: u8 = 0; |
| 236 | 236 | ||
| 237 | // Locked to avoid interleaving panic messages from multiple threads. | 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 | /// Counts how many times the panic handler is invoked by this thread. | 240 | /// Counts how many times the panic handler is invoked by this thread. |
| 241 | /// This is used to catch and handle panics triggered by the panic handler. | 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,7 +280,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 280 | // and call abort() | 280 | // and call abort() |
| 281 | 281 | ||
| 282 | // Sleep forever without hammering the CPU | 282 | // Sleep forever without hammering the CPU |
| 283 | var event: std.StaticResetEvent = .{}; | 283 | var event: std.Thread.StaticResetEvent = .{}; |
| 284 | event.wait(); | 284 | event.wait(); |
| 285 | unreachable; | 285 | unreachable; |
| 286 | } | 286 | } |
lib/std/event/lock.zig+1-1| ... | @@ -16,7 +16,7 @@ const Loop = std.event.Loop; | ... | @@ -16,7 +16,7 @@ const Loop = std.event.Loop; |
| 16 | /// Allows only one actor to hold the lock. | 16 | /// Allows only one actor to hold the lock. |
| 17 | /// TODO: make this API also work in blocking I/O mode. | 17 | /// TODO: make this API also work in blocking I/O mode. |
| 18 | pub const Lock = struct { | 18 | pub const Lock = struct { |
| 19 | mutex: std.Mutex = std.Mutex{}, | 19 | mutex: std.Thread.Mutex = std.Thread.Mutex{}, |
| 20 | head: usize = UNLOCKED, | 20 | head: usize = UNLOCKED, |
| 21 | 21 | ||
| 22 | const UNLOCKED = 0; | 22 | const UNLOCKED = 0; |
lib/std/event/loop.zig+3-3| ... | @@ -29,7 +29,7 @@ pub const Loop = struct { | ... | @@ -29,7 +29,7 @@ pub const Loop = struct { |
| 29 | fs_thread: *Thread, | 29 | fs_thread: *Thread, |
| 30 | fs_queue: std.atomic.Queue(Request), | 30 | fs_queue: std.atomic.Queue(Request), |
| 31 | fs_end_request: Request.Node, | 31 | fs_end_request: Request.Node, |
| 32 | fs_thread_wakeup: std.ResetEvent, | 32 | fs_thread_wakeup: std.Thread.ResetEvent, |
| 33 | 33 | ||
| 34 | /// For resources that have the same lifetime as the `Loop`. | 34 | /// For resources that have the same lifetime as the `Loop`. |
| 35 | /// This is only used by `Loop` for the thread pool and associated resources. | 35 | /// This is only used by `Loop` for the thread pool and associated resources. |
| ... | @@ -785,7 +785,7 @@ pub const Loop = struct { | ... | @@ -785,7 +785,7 @@ pub const Loop = struct { |
| 785 | timer: std.time.Timer, | 785 | timer: std.time.Timer, |
| 786 | waiters: Waiters, | 786 | waiters: Waiters, |
| 787 | thread: *std.Thread, | 787 | thread: *std.Thread, |
| 788 | event: std.AutoResetEvent, | 788 | event: std.Thread.AutoResetEvent, |
| 789 | is_running: bool, | 789 | is_running: bool, |
| 790 | 790 | ||
| 791 | /// Initialize the delay queue by spawning the timer thread | 791 | /// Initialize the delay queue by spawning the timer thread |
| ... | @@ -796,7 +796,7 @@ pub const Loop = struct { | ... | @@ -796,7 +796,7 @@ pub const Loop = struct { |
| 796 | .waiters = DelayQueue.Waiters{ | 796 | .waiters = DelayQueue.Waiters{ |
| 797 | .entries = std.atomic.Queue(anyframe).init(), | 797 | .entries = std.atomic.Queue(anyframe).init(), |
| 798 | }, | 798 | }, |
| 799 | .event = std.AutoResetEvent{}, | 799 | .event = std.Thread.AutoResetEvent{}, |
| 800 | .is_running = true, | 800 | .is_running = true, |
| 801 | // Must be last so that it can read the other state, such as `is_running`. | 801 | // Must be last so that it can read the other state, such as `is_running`. |
| 802 | .thread = try std.Thread.spawn(self, DelayQueue.run), | 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,7 +30,7 @@ pub fn WaitGroupGeneric(comptime counter_size: u16) type { |
| 30 | return struct { | 30 | return struct { |
| 31 | counter: CounterType = 0, | 31 | counter: CounterType = 0, |
| 32 | max_counter: CounterType = std.math.maxInt(CounterType), | 32 | max_counter: CounterType = std.math.maxInt(CounterType), |
| 33 | mutex: std.Mutex = .{}, | 33 | mutex: std.Thread.Mutex = .{}, |
| 34 | waiters: ?*Waiter = null, | 34 | waiters: ?*Waiter = null, |
| 35 | const Waiter = struct { | 35 | const Waiter = struct { |
| 36 | next: ?*Waiter, | 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,7 +750,7 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 750 | errdefer file.close(); | 750 | errdefer file.close(); |
| 751 | 751 | ||
| 752 | const S = struct { | 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 | fn checkFn(ctx: C) !void { | 754 | fn checkFn(ctx: C) !void { |
| 755 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive }); | 755 | const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive }); |
| 756 | defer file1.close(); | 756 | defer file1.close(); |
| ... | @@ -758,7 +758,7 @@ test "open file with exclusive lock twice, make sure it waits" { | ... | @@ -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 | try evt.init(); | 762 | try evt.init(); |
| 763 | defer evt.deinit(); | 763 | defer evt.deinit(); |
| 764 | 764 |
lib/std/heap/general_purpose_allocator.zig+8-8| ... | @@ -149,13 +149,13 @@ pub const Config = struct { | ... | @@ -149,13 +149,13 @@ pub const Config = struct { |
| 149 | thread_safe: bool = !std.builtin.single_threaded, | 149 | thread_safe: bool = !std.builtin.single_threaded, |
| 150 | 150 | ||
| 151 | /// What type of mutex you'd like to use, for thread safety. | 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 | 152 | /// when specfied, the mutex type must have the same shape as `std.Thread.Mutex` and |
| 153 | /// `std.mutex.Dummy`, and have no required fields. Specifying this field causes | 153 | /// `std.Thread.Mutex.Dummy`, and have no required fields. Specifying this field causes |
| 154 | /// the `thread_safe` field to be ignored. | 154 | /// the `thread_safe` field to be ignored. |
| 155 | /// | 155 | /// |
| 156 | /// when null (default): | 156 | /// when null (default): |
| 157 | /// * the mutex type defaults to `std.Mutex` when thread_safe is enabled. | 157 | /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled. |
| 158 | /// * the mutex type defaults to `std.mutex.Dummy` otherwise. | 158 | /// * the mutex type defaults to `std.Thread.Mutex.Dummy` otherwise. |
| 159 | MutexType: ?type = null, | 159 | MutexType: ?type = null, |
| 160 | 160 | ||
| 161 | /// This is a temporary debugging trick you can use to turn segfaults into more helpful | 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,9 +187,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 187 | const mutex_init = if (config.MutexType) |T| | 187 | const mutex_init = if (config.MutexType) |T| |
| 188 | T{} | 188 | T{} |
| 189 | else if (config.thread_safe) | 189 | else if (config.thread_safe) |
| 190 | std.Mutex{} | 190 | std.Thread.Mutex{} |
| 191 | else | 191 | else |
| 192 | std.mutex.Dummy{}; | 192 | std.Thread.Mutex.Dummy{}; |
| 193 | 193 | ||
| 194 | const stack_n = config.stack_trace_frames; | 194 | const stack_n = config.stack_trace_frames; |
| 195 | const one_trace_size = @sizeOf(usize) * stack_n; | 195 | const one_trace_size = @sizeOf(usize) * stack_n; |
| ... | @@ -869,9 +869,9 @@ test "realloc large object to small object" { | ... | @@ -869,9 +869,9 @@ test "realloc large object to small object" { |
| 869 | } | 869 | } |
| 870 | 870 | ||
| 871 | test "overrideable mutexes" { | 871 | test "overrideable mutexes" { |
| 872 | var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Mutex }){ | 872 | var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Thread.Mutex }){ |
| 873 | .backing_allocator = std.testing.allocator, | 873 | .backing_allocator = std.testing.allocator, |
| 874 | .mutex = std.Mutex{}, | 874 | .mutex = std.Thread.Mutex{}, |
| 875 | }; | 875 | }; |
| 876 | defer std.testing.expect(!gpa.deinit()); | 876 | defer std.testing.expect(!gpa.deinit()); |
| 877 | const allocator = &gpa.allocator; | 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,7 +15,7 @@ pub fn once(comptime f: fn () void) Once(f) { |
| 15 | pub fn Once(comptime f: fn () void) type { | 15 | pub fn Once(comptime f: fn () void) type { |
| 16 | return struct { | 16 | return struct { |
| 17 | done: bool = false, | 17 | done: bool = false, |
| 18 | mutex: std.Mutex = std.Mutex{}, | 18 | mutex: std.Thread.Mutex = std.Thread.Mutex{}, |
| 19 | 19 | ||
| 20 | /// Call the function `f`. | 20 | /// Call the function `f`. |
| 21 | /// If `call` is invoked multiple times `f` will be executed only the | 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,3 +1635,8 @@ pub const OBJECT_NAME_INFORMATION = extern struct { |
| 1635 | Name: UNICODE_STRING, | 1635 | Name: UNICODE_STRING, |
| 1636 | }; | 1636 | }; |
| 1637 | pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION; | 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,3 +289,16 @@ pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWO |
| 289 | pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(WINAPI) BOOL; | 289 | pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(WINAPI) BOOL; |
| 290 | 290 | ||
| 291 | pub extern "kernel32" fn FlushFileBuffers(hFile: HANDLE) callconv(WINAPI) BOOL; | 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,7 +13,6 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; |
| 13 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; | 13 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; |
| 14 | pub const AutoHashMap = hash_map.AutoHashMap; | 14 | pub const AutoHashMap = hash_map.AutoHashMap; |
| 15 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; | 15 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; |
| 16 | pub const AutoResetEvent = @import("auto_reset_event.zig").AutoResetEvent; | ||
| 17 | pub const BufMap = @import("buf_map.zig").BufMap; | 16 | pub const BufMap = @import("buf_map.zig").BufMap; |
| 18 | pub const BufSet = @import("buf_set.zig").BufSet; | 17 | pub const BufSet = @import("buf_set.zig").BufSet; |
| 19 | pub const ChildProcess = @import("child_process.zig").ChildProcess; | 18 | pub const ChildProcess = @import("child_process.zig").ChildProcess; |
| ... | @@ -21,26 +20,21 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM | ... | @@ -21,26 +20,21 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM |
| 21 | pub const DynLib = @import("dynamic_library.zig").DynLib; | 20 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 22 | pub const HashMap = hash_map.HashMap; | 21 | pub const HashMap = hash_map.HashMap; |
| 23 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; | 22 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; |
| 24 | pub const mutex = @import("mutex.zig"); | ||
| 25 | pub const Mutex = mutex.Mutex; | ||
| 26 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; | 23 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; |
| 27 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; | 24 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; |
| 28 | pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; | 25 | pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; |
| 29 | pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; | 26 | pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; |
| 30 | pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; | 27 | pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; |
| 31 | pub const Progress = @import("Progress.zig"); | 28 | pub const Progress = @import("Progress.zig"); |
| 32 | pub const ResetEvent = @import("ResetEvent.zig"); | ||
| 33 | pub const SemanticVersion = @import("SemanticVersion.zig"); | 29 | pub const SemanticVersion = @import("SemanticVersion.zig"); |
| 34 | pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; | 30 | pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; |
| 35 | pub const SpinLock = @import("SpinLock.zig"); | ||
| 36 | pub const StaticResetEvent = @import("StaticResetEvent.zig"); | ||
| 37 | pub const StringHashMap = hash_map.StringHashMap; | 31 | pub const StringHashMap = hash_map.StringHashMap; |
| 38 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; | 32 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; |
| 39 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; | 33 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; |
| 40 | pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged; | 34 | pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged; |
| 41 | pub const TailQueue = @import("linked_list.zig").TailQueue; | 35 | pub const TailQueue = @import("linked_list.zig").TailQueue; |
| 42 | pub const Target = @import("target.zig").Target; | 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 | pub const array_hash_map = @import("array_hash_map.zig"); | 39 | pub const array_hash_map = @import("array_hash_map.zig"); |
| 46 | pub const atomic = @import("atomic.zig"); | 40 | pub const atomic = @import("atomic.zig"); |
| ... | @@ -98,12 +92,7 @@ test "" { | ... | @@ -98,12 +92,7 @@ test "" { |
| 98 | // server is hitting OOM. TODO revert this after stage2 arrives. | 92 | // server is hitting OOM. TODO revert this after stage2 arrives. |
| 99 | _ = ChildProcess; | 93 | _ = ChildProcess; |
| 100 | _ = DynLib; | 94 | _ = DynLib; |
| 101 | _ = mutex; | ||
| 102 | _ = Mutex; | ||
| 103 | _ = Progress; | 95 | _ = Progress; |
| 104 | _ = ResetEvent; | ||
| 105 | _ = SpinLock; | ||
| 106 | _ = StaticResetEvent; | ||
| 107 | _ = Target; | 96 | _ = Target; |
| 108 | _ = Thread; | 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,7 +125,7 @@ owned_link_dir: ?std.fs.Dir, |
| 125 | color: @import("main.zig").Color = .auto, | 125 | color: @import("main.zig").Color = .auto, |
| 126 | 126 | ||
| 127 | /// This mutex guards all `Compilation` mutable state. | 127 | /// This mutex guards all `Compilation` mutable state. |
| 128 | mutex: std.Mutex = .{}, | 128 | mutex: std.Thread.Mutex = .{}, |
| 129 | 129 | ||
| 130 | test_filter: ?[]const u8, | 130 | test_filter: ?[]const u8, |
| 131 | test_name_prefix: ?[]const u8, | 131 | test_name_prefix: ?[]const u8, |
src/ThreadPool.zig+2-2| ... | @@ -6,14 +6,14 @@ | ... | @@ -6,14 +6,14 @@ |
| 6 | const std = @import("std"); | 6 | const std = @import("std"); |
| 7 | const ThreadPool = @This(); | 7 | const ThreadPool = @This(); |
| 8 | 8 | ||
| 9 | lock: std.Mutex = .{}, | 9 | lock: std.Thread.Mutex = .{}, |
| 10 | is_running: bool = true, | 10 | is_running: bool = true, |
| 11 | allocator: *std.mem.Allocator, | 11 | allocator: *std.mem.Allocator, |
| 12 | workers: []Worker, | 12 | workers: []Worker, |
| 13 | run_queue: RunQueue = .{}, | 13 | run_queue: RunQueue = .{}, |
| 14 | idle_queue: IdleQueue = .{}, | 14 | idle_queue: IdleQueue = .{}, |
| 15 | 15 | ||
| 16 | const IdleQueue = std.SinglyLinkedList(std.ResetEvent); | 16 | const IdleQueue = std.SinglyLinkedList(std.Thread.ResetEvent); |
| 17 | const RunQueue = std.SinglyLinkedList(Runnable); | 17 | const RunQueue = std.SinglyLinkedList(Runnable); |
| 18 | const Runnable = struct { | 18 | const Runnable = struct { |
| 19 | runFn: fn (*Runnable) void, | 19 | runFn: fn (*Runnable) void, |
src/WaitGroup.zig+2-2| ... | @@ -6,9 +6,9 @@ | ... | @@ -6,9 +6,9 @@ |
| 6 | const std = @import("std"); | 6 | const std = @import("std"); |
| 7 | const WaitGroup = @This(); | 7 | const WaitGroup = @This(); |
| 8 | 8 | ||
| 9 | lock: std.Mutex = .{}, | 9 | lock: std.Thread.Mutex = .{}, |
| 10 | counter: usize = 0, | 10 | counter: usize = 0, |
| 11 | event: std.ResetEvent, | 11 | event: std.Thread.ResetEvent, |
| 12 | 12 | ||
| 13 | pub fn init(self: *WaitGroup) !void { | 13 | pub fn init(self: *WaitGroup) !void { |
| 14 | self.* = .{ | 14 | self.* = .{ |