| author | |
| committer | |
| log | 18f30346291bd2471e07924af161de080935dd60 |
| tree | 609cdd73aa40f15625f896e79b9420b3e320ddcd |
| parent | 50f1856476038e57f5d2f47c751f608b0b360662 |
| signature |
* std: start removing redundant ResetEvents
* src: fix other uses of std.Thread.ResetEvent
* src: add builtin.sanitize_thread for tsan detection
* atomic: add Atomic.fence for proper fencing with tsan
* Thread: remove the other ResetEvent's and rewrite the current one
* Thread: ResetEvent docs
* zig fmt + WaitGroup.reset() fix
* src: fix build issues for ResetEvent + tsan
* Thread: ResetEvent tests
* Thread: ResetEvent module doc
* Atomic: replace llvm *p memory constraint with *m
* panicking: handle spurious wakeups in futex.wait() when waiting for abort()
* zig fmt15 files changed, 417 insertions(+), 1033 deletions(-)
CMakeLists.txt-2| ... | ... | @@ -533,11 +533,9 @@ set(ZIG_STAGE2_SOURCES |
| 533 | 533 | "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig" |
| 534 | 534 | "${CMAKE_SOURCE_DIR}/lib/std/target/x86.zig" |
| 535 | 535 | "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig" |
| 536 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/AutoResetEvent.zig" | |
| 537 | 536 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig" |
| 538 | 537 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig" |
| 539 | 538 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig" |
| 540 | "${CMAKE_SOURCE_DIR}/lib/std/Thread/StaticResetEvent.zig" | |
| 541 | 539 | "${CMAKE_SOURCE_DIR}/lib/std/time.zig" |
| 542 | 540 | "${CMAKE_SOURCE_DIR}/lib/std/treap.zig" |
| 543 | 541 | "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig" |
lib/std/Thread.zig+9-23| ... | ... | @@ -10,10 +10,8 @@ const assert = std.debug.assert; |
| 10 | 10 | const target = builtin.target; |
| 11 | 11 | const Atomic = std.atomic.Atomic; |
| 12 | 12 | |
| 13 | pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig"); | |
| 14 | 13 | pub const Futex = @import("Thread/Futex.zig"); |
| 15 | 14 | pub const ResetEvent = @import("Thread/ResetEvent.zig"); |
| 16 | pub const StaticResetEvent = @import("Thread/StaticResetEvent.zig"); | |
| 17 | 15 | pub const Mutex = @import("Thread/Mutex.zig"); |
| 18 | 16 | pub const Semaphore = @import("Thread/Semaphore.zig"); |
| 19 | 17 | pub const Condition = @import("Thread/Condition.zig"); |
| ... | ... | @@ -1078,17 +1076,13 @@ test "setName, getName" { |
| 1078 | 1076 | if (builtin.single_threaded) return error.SkipZigTest; |
| 1079 | 1077 | |
| 1080 | 1078 | const Context = struct { |
| 1081 | start_wait_event: ResetEvent = undefined, | |
| 1082 | test_done_event: ResetEvent = undefined, | |
| 1079 | start_wait_event: ResetEvent = .{}, | |
| 1080 | test_done_event: ResetEvent = .{}, | |
| 1081 | thread_done_event: ResetEvent = .{}, | |
| 1083 | 1082 | |
| 1084 | 1083 | done: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false), |
| 1085 | 1084 | thread: Thread = undefined, |
| 1086 | 1085 | |
| 1087 | fn init(self: *@This()) !void { | |
| 1088 | try self.start_wait_event.init(); | |
| 1089 | try self.test_done_event.init(); | |
| 1090 | } | |
| 1091 | ||
| 1092 | 1086 | pub fn run(ctx: *@This()) !void { |
| 1093 | 1087 | // Wait for the main thread to have set the thread field in the context. |
| 1094 | 1088 | ctx.start_wait_event.wait(); |
| ... | ... | @@ -1104,16 +1098,14 @@ test "setName, getName" { |
| 1104 | 1098 | // Signal our test is done |
| 1105 | 1099 | ctx.test_done_event.set(); |
| 1106 | 1100 | |
| 1107 | while (!ctx.done.load(.SeqCst)) { | |
| 1108 | std.time.sleep(5 * std.time.ns_per_ms); | |
| 1109 | } | |
| 1101 | // wait for the thread to property exit | |
| 1102 | ctx.thread_done_event.wait(); | |
| 1110 | 1103 | } |
| 1111 | 1104 | }; |
| 1112 | 1105 | |
| 1113 | 1106 | var context = Context{}; |
| 1114 | try context.init(); | |
| 1115 | ||
| 1116 | 1107 | var thread = try spawn(.{}, Context.run, .{&context}); |
| 1108 | ||
| 1117 | 1109 | context.thread = thread; |
| 1118 | 1110 | context.start_wait_event.set(); |
| 1119 | 1111 | context.test_done_event.wait(); |
| ... | ... | @@ -1139,16 +1131,14 @@ test "setName, getName" { |
| 1139 | 1131 | }, |
| 1140 | 1132 | } |
| 1141 | 1133 | |
| 1142 | context.done.store(true, .SeqCst); | |
| 1134 | context.thread_done_event.set(); | |
| 1143 | 1135 | thread.join(); |
| 1144 | 1136 | } |
| 1145 | 1137 | |
| 1146 | 1138 | test "std.Thread" { |
| 1147 | 1139 | // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint. |
| 1148 | _ = AutoResetEvent; | |
| 1149 | 1140 | _ = Futex; |
| 1150 | 1141 | _ = ResetEvent; |
| 1151 | _ = StaticResetEvent; | |
| 1152 | 1142 | _ = Mutex; |
| 1153 | 1143 | _ = Semaphore; |
| 1154 | 1144 | _ = Condition; |
| ... | ... | @@ -1163,9 +1153,7 @@ test "Thread.join" { |
| 1163 | 1153 | if (builtin.single_threaded) return error.SkipZigTest; |
| 1164 | 1154 | |
| 1165 | 1155 | var value: usize = 0; |
| 1166 | var event: ResetEvent = undefined; | |
| 1167 | try event.init(); | |
| 1168 | defer event.deinit(); | |
| 1156 | var event = ResetEvent{}; | |
| 1169 | 1157 | |
| 1170 | 1158 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); |
| 1171 | 1159 | thread.join(); |
| ... | ... | @@ -1177,9 +1165,7 @@ test "Thread.detach" { |
| 1177 | 1165 | if (builtin.single_threaded) return error.SkipZigTest; |
| 1178 | 1166 | |
| 1179 | 1167 | var value: usize = 0; |
| 1180 | var event: ResetEvent = undefined; | |
| 1181 | try event.init(); | |
| 1182 | defer event.deinit(); | |
| 1168 | var event = ResetEvent{}; | |
| 1183 | 1169 | |
| 1184 | 1170 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); |
| 1185 | 1171 | thread.detach(); |
lib/std/Thread/AutoResetEvent.zig deleted-222| ... | ... | @@ -1,222 +0,0 @@ |
| 1 | //! Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`. | |
| 2 | //! Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like). | |
| 3 | //! | |
| 4 | //! AutoResetEvent has 3 possible states: | |
| 5 | //! - UNSET: the AutoResetEvent is currently unset | |
| 6 | //! - SET: the AutoResetEvent was notified before a wait() was called | |
| 7 | //! - <StaticResetEvent pointer>: there is an active waiter waiting for a notification. | |
| 8 | //! | |
| 9 | //! When attempting to wait: | |
| 10 | //! if the event is unset, it registers a ResetEvent pointer to be notified when the event is set | |
| 11 | //! if the event is already set, then it consumes the notification and resets the event. | |
| 12 | //! | |
| 13 | //! When attempting to notify: | |
| 14 | //! if the event is unset, then we set the event | |
| 15 | //! if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent | |
| 16 | //! | |
| 17 | //! This ensures that the event is automatically reset after a wait() has been issued | |
| 18 | //! and avoids the race condition when using StaticResetEvent in the following scenario: | |
| 19 | //! thread 1 | thread 2 | |
| 20 | //! StaticResetEvent.wait() | | |
| 21 | //! | StaticResetEvent.set() | |
| 22 | //! | StaticResetEvent.set() | |
| 23 | //! StaticResetEvent.reset() | | |
| 24 | //! StaticResetEvent.wait() | (missed the second .set() notification above) | |
| 25 | ||
| 26 | state: usize = UNSET, | |
| 27 | ||
| 28 | const std = @import("../std.zig"); | |
| 29 | const builtin = @import("builtin"); | |
| 30 | const testing = std.testing; | |
| 31 | const assert = std.debug.assert; | |
| 32 | const StaticResetEvent = std.Thread.StaticResetEvent; | |
| 33 | const AutoResetEvent = @This(); | |
| 34 | ||
| 35 | const UNSET = 0; | |
| 36 | const SET = 1; | |
| 37 | ||
| 38 | /// the minimum alignment for the `*StaticResetEvent` created by wait*() | |
| 39 | const event_align = std.math.max(@alignOf(StaticResetEvent), 2); | |
| 40 | ||
| 41 | pub fn wait(self: *AutoResetEvent) void { | |
| 42 | self.waitFor(null) catch unreachable; | |
| 43 | } | |
| 44 | ||
| 45 | pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { | |
| 46 | return self.waitFor(timeout); | |
| 47 | } | |
| 48 | ||
| 49 | fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { | |
| 50 | // lazily initialized StaticResetEvent | |
| 51 | var reset_event: StaticResetEvent align(event_align) = undefined; | |
| 52 | var has_reset_event = false; | |
| 53 | ||
| 54 | var state = @atomicLoad(usize, &self.state, .SeqCst); | |
| 55 | while (true) { | |
| 56 | // consume a notification if there is any | |
| 57 | if (state == SET) { | |
| 58 | @atomicStore(usize, &self.state, UNSET, .SeqCst); | |
| 59 | return; | |
| 60 | } | |
| 61 | ||
| 62 | // check if theres currently a pending ResetEvent pointer already registered | |
| 63 | if (state != UNSET) { | |
| 64 | unreachable; // multiple waiting threads on the same AutoResetEvent | |
| 65 | } | |
| 66 | ||
| 67 | // lazily initialize the ResetEvent if it hasn't been already | |
| 68 | if (!has_reset_event) { | |
| 69 | has_reset_event = true; | |
| 70 | reset_event = .{}; | |
| 71 | } | |
| 72 | ||
| 73 | // Since the AutoResetEvent currently isnt set, | |
| 74 | // try to register our ResetEvent on it to wait | |
| 75 | // for a set() call from another thread. | |
| 76 | if (@cmpxchgWeak( | |
| 77 | usize, | |
| 78 | &self.state, | |
| 79 | UNSET, | |
| 80 | @ptrToInt(&reset_event), | |
| 81 | .SeqCst, | |
| 82 | .SeqCst, | |
| 83 | )) |new_state| { | |
| 84 | state = new_state; | |
| 85 | continue; | |
| 86 | } | |
| 87 | ||
| 88 | // if no timeout was specified, then just wait forever | |
| 89 | const timeout_ns = timeout orelse { | |
| 90 | reset_event.wait(); | |
| 91 | return; | |
| 92 | }; | |
| 93 | ||
| 94 | // wait with a timeout and return if signalled via set() | |
| 95 | switch (reset_event.timedWait(timeout_ns)) { | |
| 96 | .event_set => return, | |
| 97 | .timed_out => {}, | |
| 98 | } | |
| 99 | ||
| 100 | // If we timed out, we need to transition the AutoResetEvent back to UNSET. | |
| 101 | // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. | |
| 102 | state = @cmpxchgStrong( | |
| 103 | usize, | |
| 104 | &self.state, | |
| 105 | @ptrToInt(&reset_event), | |
| 106 | UNSET, | |
| 107 | .SeqCst, | |
| 108 | .SeqCst, | |
| 109 | ) orelse return error.TimedOut; | |
| 110 | ||
| 111 | // We didn't manage to unregister ourselves from the state. | |
| 112 | if (state == SET) { | |
| 113 | unreachable; // AutoResetEvent notified without waking up the waiting thread | |
| 114 | } else if (state != UNSET) { | |
| 115 | unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out | |
| 116 | } | |
| 117 | ||
| 118 | // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up. | |
| 119 | // We need to wait for it to wake up our ResetEvent before we can return and invalidate it. | |
| 120 | // We don't return error.TimedOut here as it technically notified us while we were "timing out". | |
| 121 | reset_event.wait(); | |
| 122 | return; | |
| 123 | } | |
| 124 | } | |
| 125 | ||
| 126 | pub fn set(self: *AutoResetEvent) void { | |
| 127 | var state = @atomicLoad(usize, &self.state, .SeqCst); | |
| 128 | while (true) { | |
| 129 | // If the AutoResetEvent is already set, there is nothing else left to do | |
| 130 | if (state == SET) { | |
| 131 | return; | |
| 132 | } | |
| 133 | ||
| 134 | // If the AutoResetEvent isn't set, | |
| 135 | // then try to leave a notification for the wait() thread that we set() it. | |
| 136 | if (state == UNSET) { | |
| 137 | state = @cmpxchgWeak( | |
| 138 | usize, | |
| 139 | &self.state, | |
| 140 | UNSET, | |
| 141 | SET, | |
| 142 | .SeqCst, | |
| 143 | .SeqCst, | |
| 144 | ) orelse return; | |
| 145 | continue; | |
| 146 | } | |
| 147 | ||
| 148 | // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting. | |
| 149 | // Try to acquire ownership of it so that we can wake it up. | |
| 150 | // This also resets the AutoResetEvent so that there is no race condition as defined above. | |
| 151 | if (@cmpxchgWeak( | |
| 152 | usize, | |
| 153 | &self.state, | |
| 154 | state, | |
| 155 | UNSET, | |
| 156 | .SeqCst, | |
| 157 | .SeqCst, | |
| 158 | )) |new_state| { | |
| 159 | state = new_state; | |
| 160 | continue; | |
| 161 | } | |
| 162 | ||
| 163 | const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state); | |
| 164 | reset_event.set(); | |
| 165 | return; | |
| 166 | } | |
| 167 | } | |
| 168 | ||
| 169 | test "basic usage" { | |
| 170 | // test local code paths | |
| 171 | { | |
| 172 | var event = AutoResetEvent{}; | |
| 173 | try testing.expectError(error.TimedOut, event.timedWait(1)); | |
| 174 | event.set(); | |
| 175 | event.wait(); | |
| 176 | } | |
| 177 | ||
| 178 | // test cross-thread signaling | |
| 179 | if (builtin.single_threaded) | |
| 180 | return; | |
| 181 | ||
| 182 | const Context = struct { | |
| 183 | value: u128 = 0, | |
| 184 | in: AutoResetEvent = AutoResetEvent{}, | |
| 185 | out: AutoResetEvent = AutoResetEvent{}, | |
| 186 | ||
| 187 | const Self = @This(); | |
| 188 | ||
| 189 | fn sender(self: *Self) !void { | |
| 190 | try testing.expect(self.value == 0); | |
| 191 | self.value = 1; | |
| 192 | self.out.set(); | |
| 193 | ||
| 194 | self.in.wait(); | |
| 195 | try testing.expect(self.value == 2); | |
| 196 | self.value = 3; | |
| 197 | self.out.set(); | |
| 198 | ||
| 199 | self.in.wait(); | |
| 200 | try testing.expect(self.value == 4); | |
| 201 | } | |
| 202 | ||
| 203 | fn receiver(self: *Self) !void { | |
| 204 | self.out.wait(); | |
| 205 | try testing.expect(self.value == 1); | |
| 206 | self.value = 2; | |
| 207 | self.in.set(); | |
| 208 | ||
| 209 | self.out.wait(); | |
| 210 | try testing.expect(self.value == 3); | |
| 211 | self.value = 4; | |
| 212 | self.in.set(); | |
| 213 | } | |
| 214 | }; | |
| 215 | ||
| 216 | var context = Context{}; | |
| 217 | const send_thread = try std.Thread.spawn(.{}, Context.sender, .{&context}); | |
| 218 | const recv_thread = try std.Thread.spawn(.{}, Context.receiver, .{&context}); | |
| 219 | ||
| 220 | send_thread.join(); | |
| 221 | recv_thread.join(); | |
| 222 | } |
lib/std/Thread/Futex.zig+1-1| ... | ... | @@ -809,7 +809,7 @@ const PosixImpl = struct { |
| 809 | 809 | // |
| 810 | 810 | // The pending count increment in wait() must also now use SeqCst for the update + this pending load |
| 811 | 811 | // to be in the same modification order as our load isn't using Release/Acquire to guarantee it. |
| 812 | std.atomic.fence(.SeqCst); | |
| 812 | bucket.pending.fence(.SeqCst); | |
| 813 | 813 | if (bucket.pending.load(.Monotonic) == 0) { |
| 814 | 814 | return; |
| 815 | 815 | } |
lib/std/Thread/ResetEvent.zig+205-215| ... | ... | @@ -1,291 +1,281 @@ |
| 1 | //! A thread-safe resource which supports blocking until signaled. | |
| 2 | //! This API is for kernel threads, not evented I/O. | |
| 3 | //! This API requires being initialized at runtime, and initialization | |
| 4 | //! can fail. Once initialized, the core operations cannot fail. | |
| 5 | //! If you need an abstraction that cannot fail to be initialized, see | |
| 6 | //! `std.Thread.StaticResetEvent`. However if you can handle initialization failure, | |
| 7 | //! it is preferred to use `ResetEvent`. | |
| 1 | //! ResetEvent is a thread-safe bool which can be set to true/false ("set"/"unset"). | |
| 2 | //! It can also block threads until the "bool" is set with cancellation via timed waits. | |
| 3 | //! ResetEvent can be statically initialized and is at most `@sizeOf(u64)` large. | |
| 8 | 4 | |
| 9 | const ResetEvent = @This(); | |
| 10 | 5 | const std = @import("../std.zig"); |
| 11 | 6 | const builtin = @import("builtin"); |
| 12 | const testing = std.testing; | |
| 13 | const assert = std.debug.assert; | |
| 14 | const c = std.c; | |
| 15 | const os = std.os; | |
| 16 | const time = std.time; | |
| 17 | ||
| 18 | impl: Impl, | |
| 7 | const ResetEvent = @This(); | |
| 19 | 8 | |
| 20 | pub const Impl = if (builtin.single_threaded) | |
| 21 | std.Thread.StaticResetEvent.DebugEvent | |
| 22 | else if (builtin.target.isDarwin()) | |
| 23 | DarwinEvent | |
| 24 | else if (std.Thread.use_pthreads) | |
| 25 | PosixEvent | |
| 26 | else | |
| 27 | std.Thread.StaticResetEvent.AtomicEvent; | |
| 9 | const os = std.os; | |
| 10 | const assert = std.debug.assert; | |
| 11 | const testing = std.testing; | |
| 12 | const Atomic = std.atomic.Atomic; | |
| 13 | const Futex = std.Thread.Futex; | |
| 28 | 14 | |
| 29 | pub const InitError = error{SystemResources}; | |
| 15 | impl: Impl = .{}, | |
| 30 | 16 | |
| 31 | /// After `init`, it is legal to call any other function. | |
| 32 | pub fn init(ev: *ResetEvent) InitError!void { | |
| 33 | return ev.impl.init(); | |
| 17 | /// Returns if the ResetEvent was set(). | |
| 18 | /// Once reset() is called, this returns false until the next set(). | |
| 19 | /// The memory accesses before the set() can be said to happen before isSet() returns true. | |
| 20 | pub fn isSet(self: *const ResetEvent) bool { | |
| 21 | return self.impl.isSet(); | |
| 34 | 22 | } |
| 35 | 23 | |
| 36 | /// This function is not thread-safe. | |
| 37 | /// After `deinit`, the only legal function to call is `init`. | |
| 38 | pub fn deinit(ev: *ResetEvent) void { | |
| 39 | return ev.impl.deinit(); | |
| 24 | /// Block's the callers thread until the ResetEvent is set(). | |
| 25 | /// This is effectively a more efficient version of `while (!isSet()) {}`. | |
| 26 | /// The memory accesses before the set() can be said to happen before wait() returns. | |
| 27 | pub fn wait(self: *ResetEvent) void { | |
| 28 | self.impl.wait(null) catch |err| switch (err) { | |
| 29 | error.Timeout => unreachable, // no timeout provided so we shouldn't have timed-out | |
| 30 | }; | |
| 40 | 31 | } |
| 41 | 32 | |
| 42 | /// Sets the event if not already set and wakes up all the threads waiting on | |
| 43 | /// the event. It is safe to call `set` multiple times before calling `wait`. | |
| 44 | /// However it is illegal to call `set` after `wait` is called until the event | |
| 45 | /// is `reset`. This function is thread-safe. | |
| 46 | pub fn set(ev: *ResetEvent) void { | |
| 47 | return ev.impl.set(); | |
| 33 | /// Block's the callers thread until the ResetEvent is set(), or until the corresponding timeout expires. | |
| 34 | /// If the timeout expires before the ResetEvent is set, `error.Timeout` is returned. | |
| 35 | /// This is effectively a more efficient version of `while (!isSet()) {}`. | |
| 36 | /// The memory accesses before the set() can be said to happen before timedWait() returns without error. | |
| 37 | pub fn timedWait(self: *ResetEvent, timeout_ns: u64) error{Timeout}!void { | |
| 38 | return self.impl.wait(timeout_ns); | |
| 48 | 39 | } |
| 49 | 40 | |
| 50 | /// Resets the event to its original, unset state. | |
| 51 | /// This function is *not* thread-safe. It is equivalent to calling | |
| 52 | /// `deinit` followed by `init` but without the possibility of failure. | |
| 53 | pub fn reset(ev: *ResetEvent) void { | |
| 54 | return ev.impl.reset(); | |
| 41 | /// Marks the ResetEvent as "set" and unblocks any threads in `wait()` or `timedWait()` to observe the new state. | |
| 42 | /// The ResetEvent says "set" until reset() is called, making future set() calls do nothing semantically. | |
| 43 | /// The memory accesses before set() can be said to happen before isSet() returns true or wait()/timedWait() return successfully. | |
| 44 | pub fn set(self: *ResetEvent) void { | |
| 45 | self.impl.set(); | |
| 55 | 46 | } |
| 56 | 47 | |
| 57 | /// Wait for the event to be set by blocking the current thread. | |
| 58 | /// Thread-safe. No spurious wakeups. | |
| 59 | /// Upon return from `wait`, the only functions available to be called | |
| 60 | /// in `ResetEvent` are `reset` and `deinit`. | |
| 61 | pub fn wait(ev: *ResetEvent) void { | |
| 62 | return ev.impl.wait(); | |
| 48 | /// Unmarks the ResetEvent from its "set" state if set() was called previously. | |
| 49 | /// It is undefined behavior is reset() is called while threads are blocked in wait() or timedWait(). | |
| 50 | /// Concurrent calls to set(), isSet() and reset() are allowed. | |
| 51 | pub fn reset(self: *ResetEvent) void { | |
| 52 | self.impl.reset(); | |
| 63 | 53 | } |
| 64 | 54 | |
| 65 | pub const TimedWaitResult = enum { event_set, timed_out }; | |
| 66 | ||
| 67 | /// Wait for the event to be set by blocking the current thread. | |
| 68 | /// A timeout in nanoseconds can be provided as a hint for how | |
| 69 | /// long the thread should block on the unset event before returning | |
| 70 | /// `TimedWaitResult.timed_out`. | |
| 71 | /// Thread-safe. No precision of timing is guaranteed. | |
| 72 | /// Upon return from `wait`, the only functions available to be called | |
| 73 | /// in `ResetEvent` are `reset` and `deinit`. | |
| 74 | pub fn timedWait(ev: *ResetEvent, timeout_ns: u64) TimedWaitResult { | |
| 75 | return ev.impl.timedWait(timeout_ns); | |
| 76 | } | |
| 55 | const Impl = if (builtin.single_threaded) | |
| 56 | SingleThreadedImpl | |
| 57 | else | |
| 58 | FutexImpl; | |
| 77 | 59 | |
| 78 | /// Apple has decided to not support POSIX semaphores, so we go with a | |
| 79 | /// different approach using Grand Central Dispatch. This API is exposed | |
| 80 | /// by libSystem so it is guaranteed to be available on all Darwin platforms. | |
| 81 | pub const DarwinEvent = struct { | |
| 82 | sem: c.dispatch_semaphore_t = undefined, | |
| 60 | const SingleThreadedImpl = struct { | |
| 61 | is_set: bool = false, | |
| 83 | 62 | |
| 84 | pub fn init(ev: *DarwinEvent) !void { | |
| 85 | ev.* = .{ | |
| 86 | .sem = c.dispatch_semaphore_create(0) orelse return error.SystemResources, | |
| 87 | }; | |
| 63 | fn isSet(self: *const Impl) bool { | |
| 64 | return self.is_set; | |
| 88 | 65 | } |
| 89 | 66 | |
| 90 | pub fn deinit(ev: *DarwinEvent) void { | |
| 91 | c.dispatch_release(ev.sem); | |
| 92 | ev.* = undefined; | |
| 93 | } | |
| 67 | fn wait(self: *Impl, timeout: ?u64) error{Timeout}!void { | |
| 68 | if (self.isSet()) { | |
| 69 | return; | |
| 70 | } | |
| 94 | 71 | |
| 95 | pub fn set(ev: *DarwinEvent) void { | |
| 96 | // Empirically this returns the numerical value of the semaphore. | |
| 97 | _ = c.dispatch_semaphore_signal(ev.sem); | |
| 98 | } | |
| 72 | // There are no other threads to wake us up. | |
| 73 | // So if we wait without a timeout we would never wake up. | |
| 74 | const timeout_ns = timeout orelse { | |
| 75 | unreachable; // deadlock detected | |
| 76 | }; | |
| 99 | 77 | |
| 100 | pub fn wait(ev: *DarwinEvent) void { | |
| 101 | assert(c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_FOREVER) == 0); | |
| 78 | std.time.sleep(timeout_ns); | |
| 79 | return error.Timeout; | |
| 102 | 80 | } |
| 103 | 81 | |
| 104 | pub fn timedWait(ev: *DarwinEvent, timeout_ns: u64) TimedWaitResult { | |
| 105 | const t = c.dispatch_time(c.DISPATCH_TIME_NOW, @intCast(i64, timeout_ns)); | |
| 106 | if (c.dispatch_semaphore_wait(ev.sem, t) != 0) { | |
| 107 | return .timed_out; | |
| 108 | } else { | |
| 109 | return .event_set; | |
| 110 | } | |
| 82 | fn set(self: *Impl) void { | |
| 83 | self.is_set = true; | |
| 111 | 84 | } |
| 112 | 85 | |
| 113 | pub fn reset(ev: *DarwinEvent) void { | |
| 114 | // Keep calling until the semaphore goes back down to 0. | |
| 115 | while (c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_NOW) == 0) {} | |
| 86 | fn reset(self: *Impl) void { | |
| 87 | self.is_set = false; | |
| 116 | 88 | } |
| 117 | 89 | }; |
| 118 | 90 | |
| 119 | /// POSIX semaphores must be initialized at runtime because they are allowed to | |
| 120 | /// be implemented as file descriptors, in which case initialization would require | |
| 121 | /// a syscall to open the fd. | |
| 122 | pub const PosixEvent = struct { | |
| 123 | sem: c.sem_t = undefined, | |
| 91 | const FutexImpl = struct { | |
| 92 | state: Atomic(u32) = Atomic(u32).init(unset), | |
| 124 | 93 | |
| 125 | pub fn init(ev: *PosixEvent) !void { | |
| 126 | switch (c.getErrno(c.sem_init(&ev.sem, 0, 0))) { | |
| 127 | .SUCCESS => return, | |
| 128 | else => return error.SystemResources, | |
| 129 | } | |
| 130 | } | |
| 94 | const unset = 0; | |
| 95 | const waiting = 1; | |
| 96 | const is_set = 2; | |
| 131 | 97 | |
| 132 | pub fn deinit(ev: *PosixEvent) void { | |
| 133 | assert(c.sem_destroy(&ev.sem) == 0); | |
| 134 | ev.* = undefined; | |
| 98 | fn isSet(self: *const Impl) bool { | |
| 99 | // Acquire barrier ensures memory accesses before set() happen before we return true. | |
| 100 | return self.state.load(.Acquire) == is_set; | |
| 135 | 101 | } |
| 136 | 102 | |
| 137 | pub fn set(ev: *PosixEvent) void { | |
| 138 | assert(c.sem_post(&ev.sem) == 0); | |
| 103 | fn wait(self: *Impl, timeout: ?u64) error{Timeout}!void { | |
| 104 | // Outline the slow path to allow isSet() to be inlined | |
| 105 | if (!self.isSet()) { | |
| 106 | return self.waitUntilSet(timeout); | |
| 107 | } | |
| 139 | 108 | } |
| 140 | 109 | |
| 141 | pub fn wait(ev: *PosixEvent) void { | |
| 142 | while (true) { | |
| 143 | switch (c.getErrno(c.sem_wait(&ev.sem))) { | |
| 144 | .SUCCESS => return, | |
| 145 | .INTR => continue, | |
| 146 | .INVAL => unreachable, | |
| 147 | else => unreachable, | |
| 148 | } | |
| 110 | fn waitUntilSet(self: *Impl, timeout: ?u64) error{Timeout}!void { | |
| 111 | @setCold(true); | |
| 112 | ||
| 113 | // Try to set the state from `unset` to `waiting` to indicate | |
| 114 | // to the set() thread that others are blocked on the ResetEvent. | |
| 115 | // We avoid using any strict barriers until the end when we know the ResetEvent is set. | |
| 116 | var state = self.state.load(.Monotonic); | |
| 117 | if (state == unset) { | |
| 118 | state = self.state.compareAndSwap(state, waiting, .Monotonic, .Monotonic) orelse waiting; | |
| 149 | 119 | } |
| 150 | } | |
| 151 | 120 | |
| 152 | pub fn timedWait(ev: *PosixEvent, timeout_ns: u64) TimedWaitResult { | |
| 153 | var ts: os.timespec = undefined; | |
| 154 | var timeout_abs = timeout_ns; | |
| 155 | os.clock_gettime(os.CLOCK.REALTIME, &ts) catch return .timed_out; | |
| 156 | timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s; | |
| 157 | timeout_abs += @intCast(u64, ts.tv_nsec); | |
| 158 | ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s)); | |
| 159 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s)); | |
| 160 | while (true) { | |
| 161 | switch (c.getErrno(c.sem_timedwait(&ev.sem, &ts))) { | |
| 162 | .SUCCESS => return .event_set, | |
| 163 | .INTR => continue, | |
| 164 | .INVAL => unreachable, | |
| 165 | .TIMEDOUT => return .timed_out, | |
| 166 | else => unreachable, | |
| 121 | // Wait until the ResetEvent is set since the state is waiting. | |
| 122 | if (state == waiting) { | |
| 123 | var futex_deadline = Futex.Deadline.init(timeout); | |
| 124 | while (true) { | |
| 125 | const wait_result = futex_deadline.wait(&self.state, waiting); | |
| 126 | ||
| 127 | // Check if the ResetEvent was set before possibly reporting error.Timeout below. | |
| 128 | state = self.state.load(.Monotonic); | |
| 129 | if (state != waiting) { | |
| 130 | break; | |
| 131 | } | |
| 132 | ||
| 133 | try wait_result; | |
| 167 | 134 | } |
| 168 | 135 | } |
| 136 | ||
| 137 | // Acquire barrier ensures memory accesses before set() happen before we return. | |
| 138 | assert(state == is_set); | |
| 139 | self.state.fence(.Acquire); | |
| 169 | 140 | } |
| 170 | 141 | |
| 171 | pub fn reset(ev: *PosixEvent) void { | |
| 172 | while (true) { | |
| 173 | switch (c.getErrno(c.sem_trywait(&ev.sem))) { | |
| 174 | .SUCCESS => continue, // Need to make it go to zero. | |
| 175 | .INTR => continue, | |
| 176 | .INVAL => unreachable, | |
| 177 | .AGAIN => return, // The semaphore currently has the value zero. | |
| 178 | else => unreachable, | |
| 179 | } | |
| 142 | fn set(self: *Impl) void { | |
| 143 | // Quick check if the ResetEvent is already set before doing the atomic swap below. | |
| 144 | // set() could be getting called quite often and multiple threads calling swap() increases contention unnecessarily. | |
| 145 | if (self.state.load(.Monotonic) == is_set) { | |
| 146 | return; | |
| 147 | } | |
| 148 | ||
| 149 | // Mark the ResetEvent as set and unblock all waiters waiting on it if any. | |
| 150 | // Release barrier ensures memory accesses before set() happen before the ResetEvent is observed to be "set". | |
| 151 | if (self.state.swap(is_set, .Release) == waiting) { | |
| 152 | Futex.wake(&self.state, std.math.maxInt(u32)); | |
| 180 | 153 | } |
| 181 | 154 | } |
| 155 | ||
| 156 | fn reset(self: *Impl) void { | |
| 157 | self.state.store(unset, .Monotonic); | |
| 158 | } | |
| 182 | 159 | }; |
| 183 | 160 | |
| 184 | test "basic usage" { | |
| 185 | var event: ResetEvent = undefined; | |
| 186 | try event.init(); | |
| 187 | defer event.deinit(); | |
| 161 | test "ResetEvent - smoke test" { | |
| 162 | // make sure the event is unset | |
| 163 | var event = ResetEvent{}; | |
| 164 | try testing.expectEqual(false, event.isSet()); | |
| 188 | 165 | |
| 189 | // test event setting | |
| 166 | // make sure the event gets set | |
| 190 | 167 | event.set(); |
| 168 | try testing.expectEqual(true, event.isSet()); | |
| 191 | 169 | |
| 192 | // test event resetting | |
| 170 | // make sure the event gets unset again | |
| 193 | 171 | event.reset(); |
| 172 | try testing.expectEqual(false, event.isSet()); | |
| 194 | 173 | |
| 195 | // test event waiting (non-blocking) | |
| 196 | event.set(); | |
| 197 | event.wait(); | |
| 198 | event.reset(); | |
| 174 | // waits should timeout as there's no other thread to set the event | |
| 175 | try testing.expectError(error.Timeout, event.timedWait(0)); | |
| 176 | try testing.expectError(error.Timeout, event.timedWait(std.time.ns_per_ms)); | |
| 199 | 177 | |
| 178 | // set the event again and make sure waits complete | |
| 200 | 179 | event.set(); |
| 201 | try testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1)); | |
| 180 | event.wait(); | |
| 181 | try event.timedWait(std.time.ns_per_ms); | |
| 182 | try testing.expectEqual(true, event.isSet()); | |
| 183 | } | |
| 202 | 184 | |
| 203 | // test cross-thread signaling | |
| 204 | if (builtin.single_threaded) | |
| 205 | return; | |
| 185 | test "ResetEvent - signaling" { | |
| 186 | // This test requires spawning threads | |
| 187 | if (builtin.single_threaded) { | |
| 188 | return error.SkipZigTest; | |
| 189 | } | |
| 206 | 190 | |
| 207 | 191 | const Context = struct { |
| 208 | const Self = @This(); | |
| 209 | ||
| 210 | value: u128, | |
| 211 | in: ResetEvent, | |
| 212 | out: ResetEvent, | |
| 213 | ||
| 214 | fn init(self: *Self) !void { | |
| 215 | self.* = .{ | |
| 216 | .value = 0, | |
| 217 | .in = undefined, | |
| 218 | .out = undefined, | |
| 219 | }; | |
| 220 | try self.in.init(); | |
| 221 | try self.out.init(); | |
| 222 | } | |
| 192 | in: ResetEvent = .{}, | |
| 193 | out: ResetEvent = .{}, | |
| 194 | value: usize = 0, | |
| 195 | ||
| 196 | fn input(self: *@This()) !void { | |
| 197 | // wait for the value to become 1 | |
| 198 | self.in.wait(); | |
| 199 | self.in.reset(); | |
| 200 | try testing.expectEqual(self.value, 1); | |
| 201 | ||
| 202 | // bump the value and wake up output() | |
| 203 | self.value = 2; | |
| 204 | self.out.set(); | |
| 223 | 205 | |
| 224 | fn deinit(self: *Self) void { | |
| 225 | self.in.deinit(); | |
| 226 | self.out.deinit(); | |
| 227 | self.* = undefined; | |
| 206 | // wait for output to receive 2, bump the value and wake us up with 3 | |
| 207 | self.in.wait(); | |
| 208 | self.in.reset(); | |
| 209 | try testing.expectEqual(self.value, 3); | |
| 210 | ||
| 211 | // bump the value and wake up output() for it to see 4 | |
| 212 | self.value = 4; | |
| 213 | self.out.set(); | |
| 228 | 214 | } |
| 229 | 215 | |
| 230 | fn sender(self: *Self) !void { | |
| 231 | // update value and signal input | |
| 232 | try testing.expect(self.value == 0); | |
| 216 | fn output(self: *@This()) !void { | |
| 217 | // start with 0 and bump the value for input to see 1 | |
| 218 | try testing.expectEqual(self.value, 0); | |
| 233 | 219 | self.value = 1; |
| 234 | 220 | self.in.set(); |
| 235 | 221 | |
| 236 | // wait for receiver to update value and signal output | |
| 222 | // wait for input to receive 1, bump the value to 2 and wake us up | |
| 237 | 223 | self.out.wait(); |
| 238 | try testing.expect(self.value == 2); | |
| 224 | self.out.reset(); | |
| 225 | try testing.expectEqual(self.value, 2); | |
| 239 | 226 | |
| 240 | // update value and signal final input | |
| 227 | // bump the value to 3 for input to see (rhymes) | |
| 241 | 228 | self.value = 3; |
| 242 | 229 | self.in.set(); |
| 230 | ||
| 231 | // wait for input to bump the value to 4 and receive no more (rhymes) | |
| 232 | self.out.wait(); | |
| 233 | self.out.reset(); | |
| 234 | try testing.expectEqual(self.value, 4); | |
| 243 | 235 | } |
| 236 | }; | |
| 244 | 237 | |
| 245 | fn receiver(self: *Self) !void { | |
| 246 | // wait for sender to update value and signal input | |
| 247 | self.in.wait(); | |
| 248 | try testing.expect(self.value == 1); | |
| 238 | var ctx = Context{}; | |
| 249 | 239 | |
| 250 | // update value and signal output | |
| 251 | self.in.reset(); | |
| 252 | self.value = 2; | |
| 253 | self.out.set(); | |
| 240 | const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx}); | |
| 241 | defer thread.join(); | |
| 254 | 242 | |
| 255 | // wait for sender to update value and signal final input | |
| 256 | self.in.wait(); | |
| 257 | try testing.expect(self.value == 3); | |
| 258 | } | |
| 243 | try ctx.input(); | |
| 244 | } | |
| 259 | 245 | |
| 260 | fn sleeper(self: *Self) void { | |
| 261 | self.in.set(); | |
| 262 | time.sleep(time.ns_per_ms * 2); | |
| 263 | self.value = 5; | |
| 264 | self.out.set(); | |
| 246 | test "ResetEvent - broadcast" { | |
| 247 | // This test requires spawning threads | |
| 248 | if (builtin.single_threaded) { | |
| 249 | return error.SkipZigTest; | |
| 250 | } | |
| 251 | ||
| 252 | const num_threads = 10; | |
| 253 | const Barrier = struct { | |
| 254 | event: ResetEvent = .{}, | |
| 255 | counter: Atomic(usize) = Atomic(usize).init(num_threads), | |
| 256 | ||
| 257 | fn wait(self: *@This()) void { | |
| 258 | if (self.counter.fetchSub(1, .AcqRel) == 1) { | |
| 259 | self.event.set(); | |
| 260 | } | |
| 265 | 261 | } |
| 262 | }; | |
| 266 | 263 | |
| 267 | fn timedWaiter(self: *Self) !void { | |
| 268 | self.in.wait(); | |
| 269 | try testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us)); | |
| 270 | try self.out.timedWait(time.ns_per_ms * 100); | |
| 271 | try testing.expect(self.value == 5); | |
| 264 | const Context = struct { | |
| 265 | start_barrier: Barrier = .{}, | |
| 266 | finish_barrier: Barrier = .{}, | |
| 267 | ||
| 268 | fn run(self: *@This()) void { | |
| 269 | self.start_barrier.wait(); | |
| 270 | self.finish_barrier.wait(); | |
| 272 | 271 | } |
| 273 | 272 | }; |
| 274 | 273 | |
| 275 | var context: Context = undefined; | |
| 276 | try context.init(); | |
| 277 | defer context.deinit(); | |
| 278 | const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context}); | |
| 279 | defer receiver.join(); | |
| 280 | try context.sender(); | |
| 281 | ||
| 282 | if (false) { | |
| 283 | // I have now observed this fail on macOS, Windows, and Linux. | |
| 284 | // https://github.com/ziglang/zig/issues/7009 | |
| 285 | var timed = Context.init(); | |
| 286 | defer timed.deinit(); | |
| 287 | const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed}); | |
| 288 | defer sleeper.join(); | |
| 289 | try timed.timedWaiter(); | |
| 290 | } | |
| 274 | var ctx = Context{}; | |
| 275 | var threads: [num_threads - 1]std.Thread = undefined; | |
| 276 | ||
| 277 | for (threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx}); | |
| 278 | defer for (threads) |t| t.join(); | |
| 279 | ||
| 280 | ctx.run(); | |
| 291 | 281 | } |
lib/std/Thread/StaticResetEvent.zig deleted-395| ... | ... | @@ -1,395 +0,0 @@ |
| 1 | //! A thread-safe resource which supports blocking until signaled. | |
| 2 | //! This API is for kernel threads, not evented I/O. | |
| 3 | //! This API is statically initializable. It cannot fail to be initialized | |
| 4 | //! and it requires no deinitialization. The downside is that it may not | |
| 5 | //! integrate as cleanly into other synchronization APIs, or, in a worst case, | |
| 6 | //! may be forced to fall back on spin locking. As a rule of thumb, prefer | |
| 7 | //! to use `std.Thread.ResetEvent` when possible, and use `StaticResetEvent` when | |
| 8 | //! the logic needs stronger API guarantees. | |
| 9 | ||
| 10 | const std = @import("../std.zig"); | |
| 11 | const builtin = @import("builtin"); | |
| 12 | const StaticResetEvent = @This(); | |
| 13 | const assert = std.debug.assert; | |
| 14 | const os = std.os; | |
| 15 | const time = std.time; | |
| 16 | const linux = std.os.linux; | |
| 17 | const windows = std.os.windows; | |
| 18 | const testing = std.testing; | |
| 19 | ||
| 20 | impl: Impl = .{}, | |
| 21 | ||
| 22 | pub const Impl = if (builtin.single_threaded) | |
| 23 | DebugEvent | |
| 24 | else | |
| 25 | AtomicEvent; | |
| 26 | ||
| 27 | /// Sets the event if not already set and wakes up all the threads waiting on | |
| 28 | /// the event. It is safe to call `set` multiple times before calling `wait`. | |
| 29 | /// However it is illegal to call `set` after `wait` is called until the event | |
| 30 | /// is `reset`. This function is thread-safe. | |
| 31 | pub fn set(ev: *StaticResetEvent) void { | |
| 32 | return ev.impl.set(); | |
| 33 | } | |
| 34 | ||
| 35 | /// Wait for the event to be set by blocking the current thread. | |
| 36 | /// Thread-safe. No spurious wakeups. | |
| 37 | /// Upon return from `wait`, the only function available to be called | |
| 38 | /// in `StaticResetEvent` is `reset`. | |
| 39 | pub fn wait(ev: *StaticResetEvent) void { | |
| 40 | return ev.impl.wait(); | |
| 41 | } | |
| 42 | ||
| 43 | /// Resets the event to its original, unset state. | |
| 44 | /// This function is *not* thread-safe. It is equivalent to calling | |
| 45 | /// `deinit` followed by `init` but without the possibility of failure. | |
| 46 | pub fn reset(ev: *StaticResetEvent) void { | |
| 47 | return ev.impl.reset(); | |
| 48 | } | |
| 49 | ||
| 50 | pub const TimedWaitResult = std.Thread.ResetEvent.TimedWaitResult; | |
| 51 | ||
| 52 | /// Wait for the event to be set by blocking the current thread. | |
| 53 | /// A timeout in nanoseconds can be provided as a hint for how | |
| 54 | /// long the thread should block on the unset event before returning | |
| 55 | /// `TimedWaitResult.timed_out`. | |
| 56 | /// Thread-safe. No precision of timing is guaranteed. | |
| 57 | /// Upon return from `timedWait`, the only function available to be called | |
| 58 | /// in `StaticResetEvent` is `reset`. | |
| 59 | pub fn timedWait(ev: *StaticResetEvent, timeout_ns: u64) TimedWaitResult { | |
| 60 | return ev.impl.timedWait(timeout_ns); | |
| 61 | } | |
| 62 | ||
| 63 | /// For single-threaded builds, we use this to detect deadlocks. | |
| 64 | /// In unsafe modes this ends up being no-ops. | |
| 65 | pub const DebugEvent = struct { | |
| 66 | state: State = State.unset, | |
| 67 | ||
| 68 | const State = enum { | |
| 69 | unset, | |
| 70 | set, | |
| 71 | waited, | |
| 72 | }; | |
| 73 | ||
| 74 | /// This function is provided so that this type can be re-used inside | |
| 75 | /// `std.Thread.ResetEvent`. | |
| 76 | pub fn init(ev: *DebugEvent) void { | |
| 77 | ev.* = .{}; | |
| 78 | } | |
| 79 | ||
| 80 | /// This function is provided so that this type can be re-used inside | |
| 81 | /// `std.Thread.ResetEvent`. | |
| 82 | pub fn deinit(ev: *DebugEvent) void { | |
| 83 | ev.* = undefined; | |
| 84 | } | |
| 85 | ||
| 86 | pub fn set(ev: *DebugEvent) void { | |
| 87 | switch (ev.state) { | |
| 88 | .unset => ev.state = .set, | |
| 89 | .set => {}, | |
| 90 | .waited => unreachable, // Not allowed to call `set` until `reset`. | |
| 91 | } | |
| 92 | } | |
| 93 | ||
| 94 | pub fn wait(ev: *DebugEvent) void { | |
| 95 | switch (ev.state) { | |
| 96 | .unset => unreachable, // Deadlock detected. | |
| 97 | .set => return, | |
| 98 | .waited => unreachable, // Not allowed to call `wait` until `reset`. | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult { | |
| 103 | _ = timeout; | |
| 104 | switch (ev.state) { | |
| 105 | .unset => return .timed_out, | |
| 106 | .set => return .event_set, | |
| 107 | .waited => unreachable, // Not allowed to call `wait` until `reset`. | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | pub fn reset(ev: *DebugEvent) void { | |
| 112 | ev.state = .unset; | |
| 113 | } | |
| 114 | }; | |
| 115 | ||
| 116 | pub const AtomicEvent = struct { | |
| 117 | waiters: u32 = 0, | |
| 118 | ||
| 119 | const WAKE = 1 << 0; | |
| 120 | const WAIT = 1 << 1; | |
| 121 | ||
| 122 | /// This function is provided so that this type can be re-used inside | |
| 123 | /// `std.Thread.ResetEvent`. | |
| 124 | pub fn init(ev: *AtomicEvent) void { | |
| 125 | ev.* = .{}; | |
| 126 | } | |
| 127 | ||
| 128 | /// This function is provided so that this type can be re-used inside | |
| 129 | /// `std.Thread.ResetEvent`. | |
| 130 | pub fn deinit(ev: *AtomicEvent) void { | |
| 131 | ev.* = undefined; | |
| 132 | } | |
| 133 | ||
| 134 | pub fn set(ev: *AtomicEvent) void { | |
| 135 | const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release); | |
| 136 | if (waiters >= WAIT) { | |
| 137 | return Futex.wake(&ev.waiters, waiters >> 1); | |
| 138 | } | |
| 139 | } | |
| 140 | ||
| 141 | pub fn wait(ev: *AtomicEvent) void { | |
| 142 | switch (ev.timedWait(null)) { | |
| 143 | .timed_out => unreachable, | |
| 144 | .event_set => return, | |
| 145 | } | |
| 146 | } | |
| 147 | ||
| 148 | pub fn timedWait(ev: *AtomicEvent, timeout: ?u64) TimedWaitResult { | |
| 149 | var waiters = @atomicLoad(u32, &ev.waiters, .Acquire); | |
| 150 | while (waiters != WAKE) { | |
| 151 | waiters = @cmpxchgWeak(u32, &ev.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse { | |
| 152 | if (Futex.wait(&ev.waiters, timeout)) |_| { | |
| 153 | return .event_set; | |
| 154 | } else |_| { | |
| 155 | return .timed_out; | |
| 156 | } | |
| 157 | }; | |
| 158 | } | |
| 159 | return .event_set; | |
| 160 | } | |
| 161 | ||
| 162 | pub fn reset(ev: *AtomicEvent) void { | |
| 163 | @atomicStore(u32, &ev.waiters, 0, .Monotonic); | |
| 164 | } | |
| 165 | ||
| 166 | pub const Futex = switch (builtin.os.tag) { | |
| 167 | .windows => WindowsFutex, | |
| 168 | .linux => LinuxFutex, | |
| 169 | else => SpinFutex, | |
| 170 | }; | |
| 171 | ||
| 172 | pub const SpinFutex = struct { | |
| 173 | fn wake(waiters: *u32, wake_count: u32) void { | |
| 174 | _ = waiters; | |
| 175 | _ = wake_count; | |
| 176 | } | |
| 177 | ||
| 178 | fn wait(waiters: *u32, timeout: ?u64) !void { | |
| 179 | var timer: time.Timer = undefined; | |
| 180 | if (timeout != null) | |
| 181 | timer = time.Timer.start() catch return error.TimedOut; | |
| 182 | ||
| 183 | while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { | |
| 184 | std.Thread.yield() catch std.atomic.spinLoopHint(); | |
| 185 | if (timeout) |timeout_ns| { | |
| 186 | if (timer.read() >= timeout_ns) | |
| 187 | return error.TimedOut; | |
| 188 | } | |
| 189 | } | |
| 190 | } | |
| 191 | }; | |
| 192 | ||
| 193 | pub const LinuxFutex = struct { | |
| 194 | fn wake(waiters: *u32, wake_count: u32) void { | |
| 195 | _ = wake_count; | |
| 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) == .SUCCESS); | |
| 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 | .SUCCESS => continue, | |
| 220 | .TIMEDOUT => return error.TimedOut, | |
| 221 | .INTR => continue, | |
| 222 | .AGAIN => 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 anyopaque, 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 anyopaque, 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 == windows.NTSTATUS.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 | windows.NTSTATUS.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.Thread.yield() catch std.atomic.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 | try testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1)); | |
| 324 | ||
| 325 | // test cross-thread signaling | |
| 326 | if (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 | try 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 | try 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 | try testing.expect(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 | try testing.expect(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 | try testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us)); | |
| 376 | try self.out.timedWait(time.ns_per_ms * 100); | |
| 377 | try testing.expect(self.value == 5); | |
| 378 | } | |
| 379 | }; | |
| 380 | ||
| 381 | var context = Context{}; | |
| 382 | const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context}); | |
| 383 | defer receiver.join(); | |
| 384 | try 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(.{}, Context.sleeper, .{&timed}); | |
| 392 | defer sleeper.join(); | |
| 393 | try timed.timedWaiter(); | |
| 394 | } | |
| 395 | } |
lib/std/atomic/Atomic.zig+67-13| ... | ... | @@ -14,19 +14,66 @@ pub fn Atomic(comptime T: type) type { |
| 14 | 14 | return .{ .value = value }; |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | /// Perform an atomic fence which uses the atomic value as a hint for the modification order. | |
| 18 | /// Use this when you want to imply a fence on an atomic variable without necessarily performing a memory access. | |
| 19 | /// | |
| 20 | /// Example: | |
| 21 | /// ``` | |
| 22 | /// const RefCount = struct { | |
| 23 | /// count: Atomic(usize), | |
| 24 | /// dropFn: *const fn(*RefCount) void, | |
| 25 | /// | |
| 26 | /// fn ref(self: *RefCount) void { | |
| 27 | /// _ = self.count.fetchAdd(1, .Monotonic); // no ordering necessary, just updating a counter | |
| 28 | /// } | |
| 29 | /// | |
| 30 | /// fn unref(self: *RefCount) void { | |
| 31 | /// // Release ensures code before unref() happens-before the count is decremented as dropFn could be called by then. | |
| 32 | /// if (self.count.fetchSub(1, .Release)) { | |
| 33 | /// // Acquire ensures count decrement and code before previous unrefs()s happens-before we call dropFn below. | |
| 34 | /// // NOTE: another alterative is to use .AcqRel on the fetchSub count decrement but it's extra barrier in possibly hot path. | |
| 35 | /// self.count.fence(.Acquire); | |
| 36 | /// (self.dropFn)(self); | |
| 37 | /// } | |
| 38 | /// } | |
| 39 | /// }; | |
| 40 | /// ``` | |
| 41 | pub inline fn fence(self: *Self, comptime ordering: Ordering) void { | |
| 42 | // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it. | |
| 43 | if (builtin.sanitize_thread) { | |
| 44 | const tsan = struct { | |
| 45 | extern "c" fn __tsan_acquire(addr: *anyopaque) void; | |
| 46 | extern "c" fn __tsan_release(addr: *anyopaque) void; | |
| 47 | }; | |
| 48 | ||
| 49 | const addr = @ptrCast(*anyopaque, self); | |
| 50 | return switch (ordering) { | |
| 51 | .Unordered, .Monotonic => @compileError(@tagName(ordering) ++ " only applies to atomic loads and stores"), | |
| 52 | .Acquire => tsan.__tsan_acquire(addr), | |
| 53 | .Release => tsan.__tsan_release(addr), | |
| 54 | .AcqRel, .SeqCst => { | |
| 55 | tsan.__tsan_acquire(addr); | |
| 56 | tsan.__tsan_release(addr); | |
| 57 | }, | |
| 58 | }; | |
| 59 | } | |
| 60 | ||
| 61 | return std.atomic.fence(ordering); | |
| 62 | } | |
| 63 | ||
| 17 | 64 | /// Non-atomically load from the atomic value without synchronization. |
| 18 | 65 | /// Care must be taken to avoid data-races when interacting with other atomic operations. |
| 19 | pub fn loadUnchecked(self: Self) T { | |
| 66 | pub inline fn loadUnchecked(self: Self) T { | |
| 20 | 67 | return self.value; |
| 21 | 68 | } |
| 22 | 69 | |
| 23 | 70 | /// Non-atomically store to the atomic value without synchronization. |
| 24 | 71 | /// Care must be taken to avoid data-races when interacting with other atomic operations. |
| 25 | pub fn storeUnchecked(self: *Self, value: T) void { | |
| 72 | pub inline fn storeUnchecked(self: *Self, value: T) void { | |
| 26 | 73 | self.value = value; |
| 27 | 74 | } |
| 28 | 75 | |
| 29 | pub fn load(self: *const Self, comptime ordering: Ordering) T { | |
| 76 | pub inline fn load(self: *const Self, comptime ordering: Ordering) T { | |
| 30 | 77 | return switch (ordering) { |
| 31 | 78 | .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on atomic stores"), |
| 32 | 79 | .Release => @compileError(@tagName(ordering) ++ " is only allowed on atomic stores"), |
| ... | ... | @@ -34,7 +81,7 @@ pub fn Atomic(comptime T: type) type { |
| 34 | 81 | }; |
| 35 | 82 | } |
| 36 | 83 | |
| 37 | pub fn store(self: *Self, value: T, comptime ordering: Ordering) void { | |
| 84 | pub inline fn store(self: *Self, value: T, comptime ordering: Ordering) void { | |
| 38 | 85 | return switch (ordering) { |
| 39 | 86 | .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Acquire) ++ " which is only allowed on atomic loads"), |
| 40 | 87 | .Acquire => @compileError(@tagName(ordering) ++ " is only allowed on atomic loads"), |
| ... | ... | @@ -189,21 +236,21 @@ pub fn Atomic(comptime T: type) type { |
| 189 | 236 | .Set => asm volatile ("lock btsw %[bit], %[ptr]" |
| 190 | 237 | // LLVM doesn't support u1 flag register return values |
| 191 | 238 | : [result] "={@ccc}" (-> u8), |
| 192 | : [ptr] "*p" (&self.value), | |
| 239 | : [ptr] "*m" (&self.value), | |
| 193 | 240 | [bit] "X" (@as(T, bit)), |
| 194 | 241 | : "cc", "memory" |
| 195 | 242 | ), |
| 196 | 243 | .Reset => asm volatile ("lock btrw %[bit], %[ptr]" |
| 197 | 244 | // LLVM doesn't support u1 flag register return values |
| 198 | 245 | : [result] "={@ccc}" (-> u8), |
| 199 | : [ptr] "*p" (&self.value), | |
| 246 | : [ptr] "*m" (&self.value), | |
| 200 | 247 | [bit] "X" (@as(T, bit)), |
| 201 | 248 | : "cc", "memory" |
| 202 | 249 | ), |
| 203 | 250 | .Toggle => asm volatile ("lock btcw %[bit], %[ptr]" |
| 204 | 251 | // LLVM doesn't support u1 flag register return values |
| 205 | 252 | : [result] "={@ccc}" (-> u8), |
| 206 | : [ptr] "*p" (&self.value), | |
| 253 | : [ptr] "*m" (&self.value), | |
| 207 | 254 | [bit] "X" (@as(T, bit)), |
| 208 | 255 | : "cc", "memory" |
| 209 | 256 | ), |
| ... | ... | @@ -212,21 +259,21 @@ pub fn Atomic(comptime T: type) type { |
| 212 | 259 | .Set => asm volatile ("lock btsl %[bit], %[ptr]" |
| 213 | 260 | // LLVM doesn't support u1 flag register return values |
| 214 | 261 | : [result] "={@ccc}" (-> u8), |
| 215 | : [ptr] "*p" (&self.value), | |
| 262 | : [ptr] "*m" (&self.value), | |
| 216 | 263 | [bit] "X" (@as(T, bit)), |
| 217 | 264 | : "cc", "memory" |
| 218 | 265 | ), |
| 219 | 266 | .Reset => asm volatile ("lock btrl %[bit], %[ptr]" |
| 220 | 267 | // LLVM doesn't support u1 flag register return values |
| 221 | 268 | : [result] "={@ccc}" (-> u8), |
| 222 | : [ptr] "*p" (&self.value), | |
| 269 | : [ptr] "*m" (&self.value), | |
| 223 | 270 | [bit] "X" (@as(T, bit)), |
| 224 | 271 | : "cc", "memory" |
| 225 | 272 | ), |
| 226 | 273 | .Toggle => asm volatile ("lock btcl %[bit], %[ptr]" |
| 227 | 274 | // LLVM doesn't support u1 flag register return values |
| 228 | 275 | : [result] "={@ccc}" (-> u8), |
| 229 | : [ptr] "*p" (&self.value), | |
| 276 | : [ptr] "*m" (&self.value), | |
| 230 | 277 | [bit] "X" (@as(T, bit)), |
| 231 | 278 | : "cc", "memory" |
| 232 | 279 | ), |
| ... | ... | @@ -235,21 +282,21 @@ pub fn Atomic(comptime T: type) type { |
| 235 | 282 | .Set => asm volatile ("lock btsq %[bit], %[ptr]" |
| 236 | 283 | // LLVM doesn't support u1 flag register return values |
| 237 | 284 | : [result] "={@ccc}" (-> u8), |
| 238 | : [ptr] "*p" (&self.value), | |
| 285 | : [ptr] "*m" (&self.value), | |
| 239 | 286 | [bit] "X" (@as(T, bit)), |
| 240 | 287 | : "cc", "memory" |
| 241 | 288 | ), |
| 242 | 289 | .Reset => asm volatile ("lock btrq %[bit], %[ptr]" |
| 243 | 290 | // LLVM doesn't support u1 flag register return values |
| 244 | 291 | : [result] "={@ccc}" (-> u8), |
| 245 | : [ptr] "*p" (&self.value), | |
| 292 | : [ptr] "*m" (&self.value), | |
| 246 | 293 | [bit] "X" (@as(T, bit)), |
| 247 | 294 | : "cc", "memory" |
| 248 | 295 | ), |
| 249 | 296 | .Toggle => asm volatile ("lock btcq %[bit], %[ptr]" |
| 250 | 297 | // LLVM doesn't support u1 flag register return values |
| 251 | 298 | : [result] "={@ccc}" (-> u8), |
| 252 | : [ptr] "*p" (&self.value), | |
| 299 | : [ptr] "*m" (&self.value), | |
| 253 | 300 | [bit] "X" (@as(T, bit)), |
| 254 | 301 | : "cc", "memory" |
| 255 | 302 | ), |
| ... | ... | @@ -266,6 +313,13 @@ pub fn Atomic(comptime T: type) type { |
| 266 | 313 | }; |
| 267 | 314 | } |
| 268 | 315 | |
| 316 | test "Atomic.fence" { | |
| 317 | inline for (.{ .Acquire, .Release, .AcqRel, .SeqCst }) |ordering| { | |
| 318 | var x = Atomic(usize).init(0); | |
| 319 | x.fence(ordering); | |
| 320 | } | |
| 321 | } | |
| 322 | ||
| 269 | 323 | fn atomicIntTypes() []const type { |
| 270 | 324 | comptime var bytes = 1; |
| 271 | 325 | comptime var types: []const type = &[_]type{}; |
lib/std/debug.zig+5-5| ... | ... | @@ -292,7 +292,7 @@ pub fn panicExtra( |
| 292 | 292 | |
| 293 | 293 | /// Non-zero whenever the program triggered a panic. |
| 294 | 294 | /// The counter is incremented/decremented atomically. |
| 295 | var panicking: u8 = 0; | |
| 295 | var panicking = std.atomic.Atomic(u8).init(0); | |
| 296 | 296 | |
| 297 | 297 | // Locked to avoid interleaving panic messages from multiple threads. |
| 298 | 298 | var panic_mutex = std.Thread.Mutex{}; |
| ... | ... | @@ -316,7 +316,7 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize |
| 316 | 316 | 0 => { |
| 317 | 317 | panic_stage = 1; |
| 318 | 318 | |
| 319 | _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst); | |
| 319 | _ = panicking.fetchAdd(1, .SeqCst); | |
| 320 | 320 | |
| 321 | 321 | // Make sure to release the mutex when done |
| 322 | 322 | { |
| ... | ... | @@ -337,13 +337,13 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize |
| 337 | 337 | dumpCurrentStackTrace(first_trace_addr); |
| 338 | 338 | } |
| 339 | 339 | |
| 340 | if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) { | |
| 340 | if (panicking.fetchSub(1, .SeqCst) != 1) { | |
| 341 | 341 | // Another thread is panicking, wait for the last one to finish |
| 342 | 342 | // and call abort() |
| 343 | 343 | |
| 344 | 344 | // Sleep forever without hammering the CPU |
| 345 | var event: std.Thread.StaticResetEvent = .{}; | |
| 346 | event.wait(); | |
| 345 | var futex = std.atomic.Atomic(u32).init(0); | |
| 346 | while (true) std.Thread.Futex.wait(&futex, 0); | |
| 347 | 347 | unreachable; |
| 348 | 348 | } |
| 349 | 349 | }, |
lib/std/event/loop.zig+19-14| ... | ... | @@ -8,6 +8,7 @@ const os = std.os; |
| 8 | 8 | const windows = os.windows; |
| 9 | 9 | const maxInt = std.math.maxInt; |
| 10 | 10 | const Thread = std.Thread; |
| 11 | const Atomic = std.atomic.Atomic; | |
| 11 | 12 | |
| 12 | 13 | const is_windows = builtin.os.tag == .windows; |
| 13 | 14 | |
| ... | ... | @@ -168,11 +169,9 @@ pub const Loop = struct { |
| 168 | 169 | .fs_end_request = .{ .data = .{ .msg = .end, .finish = .NoAction } }, |
| 169 | 170 | .fs_queue = std.atomic.Queue(Request).init(), |
| 170 | 171 | .fs_thread = undefined, |
| 171 | .fs_thread_wakeup = undefined, | |
| 172 | .fs_thread_wakeup = .{}, | |
| 172 | 173 | .delay_queue = undefined, |
| 173 | 174 | }; |
| 174 | try self.fs_thread_wakeup.init(); | |
| 175 | errdefer self.fs_thread_wakeup.deinit(); | |
| 176 | 175 | errdefer self.arena.deinit(); |
| 177 | 176 | |
| 178 | 177 | // We need at least one of these in case the fs thread wants to use onNextTick |
| ... | ... | @@ -202,7 +201,6 @@ pub const Loop = struct { |
| 202 | 201 | |
| 203 | 202 | pub fn deinit(self: *Loop) void { |
| 204 | 203 | self.deinitOsData(); |
| 205 | self.fs_thread_wakeup.deinit(); | |
| 206 | 204 | self.arena.deinit(); |
| 207 | 205 | self.* = undefined; |
| 208 | 206 | } |
| ... | ... | @@ -723,9 +721,7 @@ pub const Loop = struct { |
| 723 | 721 | extra_thread.join(); |
| 724 | 722 | } |
| 725 | 723 | |
| 726 | @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst); | |
| 727 | self.delay_queue.event.set(); | |
| 728 | self.delay_queue.thread.join(); | |
| 724 | self.delay_queue.deinit(); | |
| 729 | 725 | } |
| 730 | 726 | |
| 731 | 727 | /// Runs the provided function asynchronously. The function's frame is allocated |
| ... | ... | @@ -851,8 +847,8 @@ pub const Loop = struct { |
| 851 | 847 | timer: std.time.Timer, |
| 852 | 848 | waiters: Waiters, |
| 853 | 849 | thread: std.Thread, |
| 854 | event: std.Thread.AutoResetEvent, | |
| 855 | is_running: bool, | |
| 850 | event: std.Thread.ResetEvent, | |
| 851 | is_running: Atomic(bool), | |
| 856 | 852 | |
| 857 | 853 | /// Initialize the delay queue by spawning the timer thread |
| 858 | 854 | /// and starting any timer resources. |
| ... | ... | @@ -862,11 +858,19 @@ pub const Loop = struct { |
| 862 | 858 | .waiters = DelayQueue.Waiters{ |
| 863 | 859 | .entries = std.atomic.Queue(anyframe).init(), |
| 864 | 860 | }, |
| 865 | .event = std.Thread.AutoResetEvent{}, | |
| 866 | .is_running = true, | |
| 867 | // Must be last so that it can read the other state, such as `is_running`. | |
| 868 | .thread = try std.Thread.spawn(.{}, DelayQueue.run, .{self}), | |
| 861 | .thread = undefined, | |
| 862 | .event = .{}, | |
| 863 | .is_running = Atomic(bool).init(true), | |
| 869 | 864 | }; |
| 865 | ||
| 866 | // Must be after init so that it can read the other state, such as `is_running`. | |
| 867 | self.thread = try std.Thread.spawn(.{}, DelayQueue.run, .{self}); | |
| 868 | } | |
| 869 | ||
| 870 | fn deinit(self: *DelayQueue) void { | |
| 871 | self.is_running.store(false, .SeqCst); | |
| 872 | self.event.set(); | |
| 873 | self.thread.join(); | |
| 870 | 874 | } |
| 871 | 875 | |
| 872 | 876 | /// Entry point for the timer thread |
| ... | ... | @@ -874,7 +878,8 @@ pub const Loop = struct { |
| 874 | 878 | fn run(self: *DelayQueue) void { |
| 875 | 879 | const loop = @fieldParentPtr(Loop, "delay_queue", self); |
| 876 | 880 | |
| 877 | while (@atomicLoad(bool, &self.is_running, .SeqCst)) { | |
| 881 | while (self.is_running.load(.SeqCst)) { | |
| 882 | self.event.reset(); | |
| 878 | 883 | const now = self.timer.read(); |
| 879 | 884 | |
| 880 | 885 | if (self.waiters.popExpired(now)) |entry| { |
lib/std/fs/test.zig+20-17| ... | ... | @@ -917,7 +917,7 @@ test "open file with exclusive and shared nonblocking lock" { |
| 917 | 917 | try testing.expectError(error.WouldBlock, file2); |
| 918 | 918 | } |
| 919 | 919 | |
| 920 | test "open file with exclusive lock twice, make sure it waits" { | |
| 920 | test "open file with exclusive lock twice, make sure second lock waits" { | |
| 921 | 921 | if (builtin.single_threaded) return error.SkipZigTest; |
| 922 | 922 | |
| 923 | 923 | if (std.io.is_async) { |
| ... | ... | @@ -934,30 +934,33 @@ test "open file with exclusive lock twice, make sure it waits" { |
| 934 | 934 | errdefer file.close(); |
| 935 | 935 | |
| 936 | 936 | const S = struct { |
| 937 | fn checkFn(dir: *fs.Dir, evt: *std.Thread.ResetEvent) !void { | |
| 937 | fn checkFn(dir: *fs.Dir, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { | |
| 938 | started.set(); | |
| 938 | 939 | const file1 = try dir.createFile(filename, .{ .lock = .Exclusive }); |
| 939 | defer file1.close(); | |
| 940 | evt.set(); | |
| 940 | ||
| 941 | locked.set(); | |
| 942 | file1.close(); | |
| 941 | 943 | } |
| 942 | 944 | }; |
| 943 | 945 | |
| 944 | var evt: std.Thread.ResetEvent = undefined; | |
| 945 | try evt.init(); | |
| 946 | defer evt.deinit(); | |
| 946 | var started = std.Thread.ResetEvent{}; | |
| 947 | var locked = std.Thread.ResetEvent{}; | |
| 947 | 948 | |
| 948 | const t = try std.Thread.spawn(.{}, S.checkFn, .{ &tmp.dir, &evt }); | |
| 949 | const t = try std.Thread.spawn(.{}, S.checkFn, .{ | |
| 950 | &tmp.dir, | |
| 951 | &started, | |
| 952 | &locked, | |
| 953 | }); | |
| 949 | 954 | defer t.join(); |
| 950 | 955 | |
| 951 | const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms; | |
| 952 | // Make sure we've slept enough. | |
| 953 | var timer = try std.time.Timer.start(); | |
| 954 | while (true) { | |
| 955 | std.time.sleep(SLEEP_TIMEOUT_NS); | |
| 956 | if (timer.read() >= SLEEP_TIMEOUT_NS) break; | |
| 957 | } | |
| 956 | // Wait for the spawned thread to start trying to acquire the exclusive file lock. | |
| 957 | // Then wait a bit to make sure that can't acquire it since we currently hold the file lock. | |
| 958 | started.wait(); | |
| 959 | try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms)); | |
| 960 | ||
| 961 | // Release the file lock which should unlock the thread to lock it and set the locked event. | |
| 958 | 962 | file.close(); |
| 959 | // No timeout to avoid failures on heavily loaded systems. | |
| 960 | evt.wait(); | |
| 963 | locked.wait(); | |
| 961 | 964 | } |
| 962 | 965 | |
| 963 | 966 | test "open file with exclusive nonblocking lock twice (absolute paths)" { |
src/Compilation.zig+4-13| ... | ... | @@ -163,8 +163,8 @@ emit_llvm_bc: ?EmitLoc, |
| 163 | 163 | emit_analysis: ?EmitLoc, |
| 164 | 164 | emit_docs: ?EmitLoc, |
| 165 | 165 | |
| 166 | work_queue_wait_group: WaitGroup, | |
| 167 | astgen_wait_group: WaitGroup, | |
| 166 | work_queue_wait_group: WaitGroup = .{}, | |
| 167 | astgen_wait_group: WaitGroup = .{}, | |
| 168 | 168 | |
| 169 | 169 | /// Exported symbol names. This is only for when the target is wasm. |
| 170 | 170 | /// TODO: Remove this when Stage2 becomes the default compiler as it will already have this information. |
| ... | ... | @@ -1674,19 +1674,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { |
| 1674 | 1674 | .test_evented_io = options.test_evented_io, |
| 1675 | 1675 | .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs, |
| 1676 | 1676 | .debug_compile_errors = options.debug_compile_errors, |
| 1677 | .work_queue_wait_group = undefined, | |
| 1678 | .astgen_wait_group = undefined, | |
| 1679 | 1677 | }; |
| 1680 | 1678 | break :comp comp; |
| 1681 | 1679 | }; |
| 1682 | 1680 | errdefer comp.destroy(); |
| 1683 | 1681 | |
| 1684 | try comp.work_queue_wait_group.init(); | |
| 1685 | errdefer comp.work_queue_wait_group.deinit(); | |
| 1686 | ||
| 1687 | try comp.astgen_wait_group.init(); | |
| 1688 | errdefer comp.astgen_wait_group.deinit(); | |
| 1689 | ||
| 1690 | 1682 | // Add a `CObject` for each `c_source_files`. |
| 1691 | 1683 | try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len); |
| 1692 | 1684 | for (options.c_source_files) |c_source_file| { |
| ... | ... | @@ -1894,9 +1886,6 @@ pub fn destroy(self: *Compilation) void { |
| 1894 | 1886 | self.cache_parent.manifest_dir.close(); |
| 1895 | 1887 | if (self.owned_link_dir) |*dir| dir.close(); |
| 1896 | 1888 | |
| 1897 | self.work_queue_wait_group.deinit(); | |
| 1898 | self.astgen_wait_group.deinit(); | |
| 1899 | ||
| 1900 | 1889 | for (self.export_symbol_names.items) |symbol_name| { |
| 1901 | 1890 | gpa.free(symbol_name); |
| 1902 | 1891 | } |
| ... | ... | @@ -4701,6 +4690,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca |
| 4701 | 4690 | \\pub const link_libcpp = {}; |
| 4702 | 4691 | \\pub const have_error_return_tracing = {}; |
| 4703 | 4692 | \\pub const valgrind_support = {}; |
| 4693 | \\pub const sanitize_thread = {}; | |
| 4704 | 4694 | \\pub const position_independent_code = {}; |
| 4705 | 4695 | \\pub const position_independent_executable = {}; |
| 4706 | 4696 | \\pub const strip_debug_info = {}; |
| ... | ... | @@ -4713,6 +4703,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca |
| 4713 | 4703 | comp.bin_file.options.link_libcpp, |
| 4714 | 4704 | comp.bin_file.options.error_return_tracing, |
| 4715 | 4705 | comp.bin_file.options.valgrind, |
| 4706 | comp.bin_file.options.tsan, | |
| 4716 | 4707 | comp.bin_file.options.pic, |
| 4717 | 4708 | comp.bin_file.options.pie, |
| 4718 | 4709 | comp.bin_file.options.strip, |
src/ThreadPool.zig+64-75| ... | ... | @@ -3,13 +3,12 @@ const builtin = @import("builtin"); |
| 3 | 3 | const ThreadPool = @This(); |
| 4 | 4 | |
| 5 | 5 | mutex: std.Thread.Mutex = .{}, |
| 6 | cond: std.Thread.Condition = .{}, | |
| 7 | run_queue: RunQueue = .{}, | |
| 6 | 8 | is_running: bool = true, |
| 7 | 9 | allocator: std.mem.Allocator, |
| 8 | workers: []Worker, | |
| 9 | run_queue: RunQueue = .{}, | |
| 10 | idle_queue: IdleQueue = .{}, | |
| 10 | threads: []std.Thread, | |
| 11 | 11 | |
| 12 | const IdleQueue = std.SinglyLinkedList(std.Thread.ResetEvent); | |
| 13 | 12 | const RunQueue = std.SinglyLinkedList(Runnable); |
| 14 | 13 | const Runnable = struct { |
| 15 | 14 | runFn: RunProto, |
| ... | ... | @@ -20,89 +19,52 @@ const RunProto = switch (builtin.zig_backend) { |
| 20 | 19 | else => *const fn (*Runnable) void, |
| 21 | 20 | }; |
| 22 | 21 | |
| 23 | const Worker = struct { | |
| 24 | pool: *ThreadPool, | |
| 25 | thread: std.Thread, | |
| 26 | /// The node is for this worker only and must have an already initialized event | |
| 27 | /// when the thread is spawned. | |
| 28 | idle_node: IdleQueue.Node, | |
| 29 | ||
| 30 | fn run(worker: *Worker) void { | |
| 31 | const pool = worker.pool; | |
| 32 | ||
| 33 | while (true) { | |
| 34 | pool.mutex.lock(); | |
| 35 | ||
| 36 | if (pool.run_queue.popFirst()) |run_node| { | |
| 37 | pool.mutex.unlock(); | |
| 38 | (run_node.data.runFn)(&run_node.data); | |
| 39 | continue; | |
| 40 | } | |
| 41 | ||
| 42 | if (pool.is_running) { | |
| 43 | worker.idle_node.data.reset(); | |
| 44 | ||
| 45 | pool.idle_queue.prepend(&worker.idle_node); | |
| 46 | pool.mutex.unlock(); | |
| 47 | ||
| 48 | worker.idle_node.data.wait(); | |
| 49 | continue; | |
| 50 | } | |
| 51 | ||
| 52 | pool.mutex.unlock(); | |
| 53 | return; | |
| 54 | } | |
| 55 | } | |
| 56 | }; | |
| 57 | ||
| 58 | 22 | pub fn init(self: *ThreadPool, allocator: std.mem.Allocator) !void { |
| 59 | 23 | self.* = .{ |
| 60 | 24 | .allocator = allocator, |
| 61 | .workers = &[_]Worker{}, | |
| 25 | .threads = &[_]std.Thread{}, | |
| 62 | 26 | }; |
| 63 | if (builtin.single_threaded) | |
| 64 | return; | |
| 65 | 27 | |
| 66 | const worker_count = std.math.max(1, std.Thread.getCpuCount() catch 1); | |
| 67 | self.workers = try allocator.alloc(Worker, worker_count); | |
| 68 | errdefer allocator.free(self.workers); | |
| 28 | if (builtin.single_threaded) { | |
| 29 | return; | |
| 30 | } | |
| 69 | 31 | |
| 70 | var worker_index: usize = 0; | |
| 71 | errdefer self.destroyWorkers(worker_index); | |
| 72 | while (worker_index < worker_count) : (worker_index += 1) { | |
| 73 | const worker = &self.workers[worker_index]; | |
| 74 | worker.pool = self; | |
| 32 | const thread_count = std.math.max(1, std.Thread.getCpuCount() catch 1); | |
| 33 | self.threads = try allocator.alloc(std.Thread, thread_count); | |
| 34 | errdefer allocator.free(self.threads); | |
| 75 | 35 | |
| 76 | // Each worker requires its ResetEvent to be pre-initialized. | |
| 77 | try worker.idle_node.data.init(); | |
| 78 | errdefer worker.idle_node.data.deinit(); | |
| 36 | // kill and join any threads we spawned previously on error. | |
| 37 | var spawned: usize = 0; | |
| 38 | errdefer self.join(spawned); | |
| 79 | 39 | |
| 80 | worker.thread = try std.Thread.spawn(.{}, Worker.run, .{worker}); | |
| 40 | for (self.threads) |*thread| { | |
| 41 | thread.* = try std.Thread.spawn(.{}, worker, .{self}); | |
| 42 | spawned += 1; | |
| 81 | 43 | } |
| 82 | 44 | } |
| 83 | 45 | |
| 84 | fn destroyWorkers(self: *ThreadPool, spawned: usize) void { | |
| 85 | if (builtin.single_threaded) | |
| 86 | return; | |
| 87 | ||
| 88 | for (self.workers[0..spawned]) |*worker| { | |
| 89 | worker.thread.join(); | |
| 90 | worker.idle_node.data.deinit(); | |
| 91 | } | |
| 46 | pub fn deinit(self: *ThreadPool) void { | |
| 47 | self.join(self.threads.len); // kill and join all threads. | |
| 48 | self.* = undefined; | |
| 92 | 49 | } |
| 93 | 50 | |
| 94 | pub fn deinit(self: *ThreadPool) void { | |
| 51 | fn join(self: *ThreadPool, spawned: usize) void { | |
| 95 | 52 | { |
| 96 | 53 | self.mutex.lock(); |
| 97 | 54 | defer self.mutex.unlock(); |
| 98 | 55 | |
| 56 | // ensure future worker threads exit the dequeue loop | |
| 99 | 57 | self.is_running = false; |
| 100 | while (self.idle_queue.popFirst()) |idle_node| | |
| 101 | idle_node.data.set(); | |
| 102 | 58 | } |
| 103 | 59 | |
| 104 | self.destroyWorkers(self.workers.len); | |
| 105 | self.allocator.free(self.workers); | |
| 60 | // wake up any sleeping threads (this can be done outside the mutex) | |
| 61 | // then wait for all the threads we know are spawned to complete. | |
| 62 | self.cond.broadcast(); | |
| 63 | for (self.threads[0..spawned]) |thread| { | |
| 64 | thread.join(); | |
| 65 | } | |
| 66 | ||
| 67 | self.allocator.free(self.threads); | |
| 106 | 68 | } |
| 107 | 69 | |
| 108 | 70 | pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| ... | ... | @@ -122,24 +84,51 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void { |
| 122 | 84 | const closure = @fieldParentPtr(@This(), "run_node", run_node); |
| 123 | 85 | @call(.{}, func, closure.arguments); |
| 124 | 86 | |
| 87 | // The thread pool's allocator is protected by the mutex. | |
| 125 | 88 | const mutex = &closure.pool.mutex; |
| 126 | 89 | mutex.lock(); |
| 127 | 90 | defer mutex.unlock(); |
| 91 | ||
| 128 | 92 | closure.pool.allocator.destroy(closure); |
| 129 | 93 | } |
| 130 | 94 | }; |
| 131 | 95 | |
| 96 | { | |
| 97 | self.mutex.lock(); | |
| 98 | defer self.mutex.unlock(); | |
| 99 | ||
| 100 | const closure = try self.allocator.create(Closure); | |
| 101 | closure.* = .{ | |
| 102 | .arguments = args, | |
| 103 | .pool = self, | |
| 104 | }; | |
| 105 | ||
| 106 | self.run_queue.prepend(&closure.run_node); | |
| 107 | } | |
| 108 | ||
| 109 | // Notify waiting threads outside the lock to try and keep the critical section small. | |
| 110 | self.cond.signal(); | |
| 111 | } | |
| 112 | ||
| 113 | fn worker(self: *ThreadPool) void { | |
| 132 | 114 | self.mutex.lock(); |
| 133 | 115 | defer self.mutex.unlock(); |
| 134 | 116 | |
| 135 | const closure = try self.allocator.create(Closure); | |
| 136 | closure.* = .{ | |
| 137 | .arguments = args, | |
| 138 | .pool = self, | |
| 139 | }; | |
| 117 | while (true) { | |
| 118 | while (self.run_queue.popFirst()) |run_node| { | |
| 119 | // Temporarily unlock the mutex in order to execute the run_node | |
| 120 | self.mutex.unlock(); | |
| 121 | defer self.mutex.lock(); | |
| 140 | 122 | |
| 141 | self.run_queue.prepend(&closure.run_node); | |
| 123 | const runFn = run_node.data.runFn; | |
| 124 | runFn(&run_node.data); | |
| 125 | } | |
| 142 | 126 | |
| 143 | if (self.idle_queue.popFirst()) |idle_node| | |
| 144 | idle_node.data.set(); | |
| 127 | // Stop executing instead of waiting if the thread pool is no longer running. | |
| 128 | if (self.is_running) { | |
| 129 | self.cond.wait(&self.mutex); | |
| 130 | } else { | |
| 131 | break; | |
| 132 | } | |
| 133 | } | |
| 145 | 134 | } |
src/WaitGroup.zig+16-33| ... | ... | @@ -1,56 +1,39 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const Atomic = std.atomic.Atomic; | |
| 3 | const assert = std.debug.assert; | |
| 2 | 4 | const WaitGroup = @This(); |
| 3 | 5 | |
| 4 | mutex: std.Thread.Mutex = .{}, | |
| 5 | counter: usize = 0, | |
| 6 | event: std.Thread.ResetEvent, | |
| 7 | ||
| 8 | pub fn init(self: *WaitGroup) !void { | |
| 9 | self.* = .{ | |
| 10 | .mutex = .{}, | |
| 11 | .counter = 0, | |
| 12 | .event = undefined, | |
| 13 | }; | |
| 14 | try self.event.init(); | |
| 15 | } | |
| 6 | const is_waiting: usize = 1 << 0; | |
| 7 | const one_pending: usize = 1 << 1; | |
| 16 | 8 | |
| 17 | pub fn deinit(self: *WaitGroup) void { | |
| 18 | self.event.deinit(); | |
| 19 | self.* = undefined; | |
| 20 | } | |
| 9 | state: Atomic(usize) = Atomic(usize).init(0), | |
| 10 | event: std.Thread.ResetEvent = .{}, | |
| 21 | 11 | |
| 22 | 12 | pub fn start(self: *WaitGroup) void { |
| 23 | self.mutex.lock(); | |
| 24 | defer self.mutex.unlock(); | |
| 25 | ||
| 26 | self.counter += 1; | |
| 13 | const state = self.state.fetchAdd(one_pending, .Monotonic); | |
| 14 | assert((state / one_pending) < (std.math.maxInt(usize) / one_pending)); | |
| 27 | 15 | } |
| 28 | 16 | |
| 29 | 17 | pub fn finish(self: *WaitGroup) void { |
| 30 | self.mutex.lock(); | |
| 31 | defer self.mutex.unlock(); | |
| 18 | const state = self.state.fetchSub(one_pending, .Release); | |
| 19 | assert((state / one_pending) > 0); | |
| 32 | 20 | |
| 33 | self.counter -= 1; | |
| 34 | ||
| 35 | if (self.counter == 0) { | |
| 21 | if (state == (one_pending | is_waiting)) { | |
| 22 | self.state.fence(.Acquire); | |
| 36 | 23 | self.event.set(); |
| 37 | 24 | } |
| 38 | 25 | } |
| 39 | 26 | |
| 40 | 27 | pub fn wait(self: *WaitGroup) void { |
| 41 | while (true) { | |
| 42 | self.mutex.lock(); | |
| 43 | ||
| 44 | if (self.counter == 0) { | |
| 45 | self.mutex.unlock(); | |
| 46 | return; | |
| 47 | } | |
| 28 | var state = self.state.fetchAdd(is_waiting, .Acquire); | |
| 29 | assert(state & is_waiting == 0); | |
| 48 | 30 | |
| 49 | self.mutex.unlock(); | |
| 31 | if ((state / one_pending) > 0) { | |
| 50 | 32 | self.event.wait(); |
| 51 | 33 | } |
| 52 | 34 | } |
| 53 | 35 | |
| 54 | 36 | pub fn reset(self: *WaitGroup) void { |
| 37 | self.state.store(0, .Monotonic); | |
| 55 | 38 | self.event.reset(); |
| 56 | 39 | } |
src/crash_report.zig+6-5| ... | ... | @@ -362,7 +362,7 @@ const PanicSwitch = struct { |
| 362 | 362 | /// Updated atomically before taking the panic_mutex. |
| 363 | 363 | /// In recoverable cases, the program will not abort |
| 364 | 364 | /// until all panicking threads have dumped their traces. |
| 365 | var panicking: u8 = 0; | |
| 365 | var panicking = std.atomic.Atomic(u8).init(0); | |
| 366 | 366 | |
| 367 | 367 | // Locked to avoid interleaving panic messages from multiple threads. |
| 368 | 368 | var panic_mutex = std.Thread.Mutex{}; |
| ... | ... | @@ -430,7 +430,7 @@ const PanicSwitch = struct { |
| 430 | 430 | }; |
| 431 | 431 | state.* = new_state; |
| 432 | 432 | |
| 433 | _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst); | |
| 433 | _ = panicking.fetchAdd(1, .SeqCst); | |
| 434 | 434 | |
| 435 | 435 | state.recover_stage = .release_ref_count; |
| 436 | 436 | |
| ... | ... | @@ -512,13 +512,14 @@ const PanicSwitch = struct { |
| 512 | 512 | noinline fn releaseRefCount(state: *volatile PanicState) noreturn { |
| 513 | 513 | state.recover_stage = .abort; |
| 514 | 514 | |
| 515 | if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) { | |
| 515 | if (panicking.fetchSub(1, .SeqCst) != 1) { | |
| 516 | 516 | // Another thread is panicking, wait for the last one to finish |
| 517 | 517 | // and call abort() |
| 518 | 518 | |
| 519 | 519 | // Sleep forever without hammering the CPU |
| 520 | var event: std.Thread.StaticResetEvent = .{}; | |
| 521 | event.wait(); | |
| 520 | var futex = std.atomic.Atomic(u32).init(0); | |
| 521 | while (true) std.Thread.Futex.wait(&futex, 0); | |
| 522 | ||
| 522 | 523 | // This should be unreachable, recurse into recoverAbort. |
| 523 | 524 | @panic("event.wait() returned"); |
| 524 | 525 | } |
src/stage1/codegen.cpp+1| ... | ... | @@ -9993,6 +9993,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { |
| 9993 | 9993 | buf_appendf(contents, "pub const link_libcpp = %s;\n", bool_to_str(g->link_libcpp)); |
| 9994 | 9994 | buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing)); |
| 9995 | 9995 | buf_appendf(contents, "pub const valgrind_support = false;\n"); |
| 9996 | buf_appendf(contents, "pub const sanitize_thread = false;\n"); | |
| 9996 | 9997 | buf_appendf(contents, "pub const position_independent_code = %s;\n", bool_to_str(g->have_pic)); |
| 9997 | 9998 | buf_appendf(contents, "pub const position_independent_executable = %s;\n", bool_to_str(g->have_pie)); |
| 9998 | 9999 | buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols)); |