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