authorgravatar for 45520026+kprotty@users.noreply.github.comprotty <45520026+kprotty@users.noreply.github.com> 2022-04-26 16:48:56-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-04-26 16:48:56-05:00
log18f30346291bd2471e07924af161de080935dd60
tree609cdd73aa40f15625f896e79b9420b3e320ddcd
parent50f1856476038e57f5d2f47c751f608b0b360662
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.Thread: ResetEvent improvements (#11523)

* 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 fmt

15 files changed, 417 insertions(+), 1033 deletions(-)

CMakeLists.txt-2
......@@ -533,11 +533,9 @@ set(ZIG_STAGE2_SOURCES
533533 "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig"
534534 "${CMAKE_SOURCE_DIR}/lib/std/target/x86.zig"
535535 "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig"
536 "${CMAKE_SOURCE_DIR}/lib/std/Thread/AutoResetEvent.zig"
537536 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig"
538537 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig"
539538 "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig"
540 "${CMAKE_SOURCE_DIR}/lib/std/Thread/StaticResetEvent.zig"
541539 "${CMAKE_SOURCE_DIR}/lib/std/time.zig"
542540 "${CMAKE_SOURCE_DIR}/lib/std/treap.zig"
543541 "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig"
lib/std/Thread.zig+9-23
......@@ -10,10 +10,8 @@ const assert = std.debug.assert;
1010const target = builtin.target;
1111const Atomic = std.atomic.Atomic;
1212
13pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig");
1413pub const Futex = @import("Thread/Futex.zig");
1514pub const ResetEvent = @import("Thread/ResetEvent.zig");
16pub const StaticResetEvent = @import("Thread/StaticResetEvent.zig");
1715pub const Mutex = @import("Thread/Mutex.zig");
1816pub const Semaphore = @import("Thread/Semaphore.zig");
1917pub const Condition = @import("Thread/Condition.zig");
......@@ -1078,17 +1076,13 @@ test "setName, getName" {
10781076 if (builtin.single_threaded) return error.SkipZigTest;
10791077
10801078 const Context = struct {
1081 start_wait_event: ResetEvent = undefined,
1082 test_done_event: ResetEvent = undefined,
1079 start_wait_event: ResetEvent = .{},
1080 test_done_event: ResetEvent = .{},
1081 thread_done_event: ResetEvent = .{},
10831082
10841083 done: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
10851084 thread: Thread = undefined,
10861085
1087 fn init(self: *@This()) !void {
1088 try self.start_wait_event.init();
1089 try self.test_done_event.init();
1090 }
1091
10921086 pub fn run(ctx: *@This()) !void {
10931087 // Wait for the main thread to have set the thread field in the context.
10941088 ctx.start_wait_event.wait();
......@@ -1104,16 +1098,14 @@ test "setName, getName" {
11041098 // Signal our test is done
11051099 ctx.test_done_event.set();
11061100
1107 while (!ctx.done.load(.SeqCst)) {
1108 std.time.sleep(5 * std.time.ns_per_ms);
1109 }
1101 // wait for the thread to property exit
1102 ctx.thread_done_event.wait();
11101103 }
11111104 };
11121105
11131106 var context = Context{};
1114 try context.init();
1115
11161107 var thread = try spawn(.{}, Context.run, .{&context});
1108
11171109 context.thread = thread;
11181110 context.start_wait_event.set();
11191111 context.test_done_event.wait();
......@@ -1139,16 +1131,14 @@ test "setName, getName" {
11391131 },
11401132 }
11411133
1142 context.done.store(true, .SeqCst);
1134 context.thread_done_event.set();
11431135 thread.join();
11441136}
11451137
11461138test "std.Thread" {
11471139 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
1148 _ = AutoResetEvent;
11491140 _ = Futex;
11501141 _ = ResetEvent;
1151 _ = StaticResetEvent;
11521142 _ = Mutex;
11531143 _ = Semaphore;
11541144 _ = Condition;
......@@ -1163,9 +1153,7 @@ test "Thread.join" {
11631153 if (builtin.single_threaded) return error.SkipZigTest;
11641154
11651155 var value: usize = 0;
1166 var event: ResetEvent = undefined;
1167 try event.init();
1168 defer event.deinit();
1156 var event = ResetEvent{};
11691157
11701158 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
11711159 thread.join();
......@@ -1177,9 +1165,7 @@ test "Thread.detach" {
11771165 if (builtin.single_threaded) return error.SkipZigTest;
11781166
11791167 var value: usize = 0;
1180 var event: ResetEvent = undefined;
1181 try event.init();
1182 defer event.deinit();
1168 var event = ResetEvent{};
11831169
11841170 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
11851171 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
26state: usize = UNSET,
27
28const std = @import("../std.zig");
29const builtin = @import("builtin");
30const testing = std.testing;
31const assert = std.debug.assert;
32const StaticResetEvent = std.Thread.StaticResetEvent;
33const AutoResetEvent = @This();
34
35const UNSET = 0;
36const SET = 1;
37
38/// the minimum alignment for the `*StaticResetEvent` created by wait*()
39const event_align = std.math.max(@alignOf(StaticResetEvent), 2);
40
41pub fn wait(self: *AutoResetEvent) void {
42 self.waitFor(null) catch unreachable;
43}
44
45pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void {
46 return self.waitFor(timeout);
47}
48
49fn 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
126pub 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
169test "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 {
809809 //
810810 // The pending count increment in wait() must also now use SeqCst for the update + this pending load
811811 // 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);
813813 if (bucket.pending.load(.Monotonic) == 0) {
814814 return;
815815 }
lib/std/Thread/ResetEvent.zig+205-215
......@@ -1,291 +1,281 @@
1//! A thread-safe resource which supports blocking until signaled.
2//! This API is for kernel threads, not evented I/O.
3//! This API requires being initialized at runtime, and initialization
4//! can fail. Once initialized, the core operations cannot fail.
5//! If you need an abstraction that cannot fail to be initialized, see
6//! `std.Thread.StaticResetEvent`. However if you can handle initialization failure,
7//! it is preferred to use `ResetEvent`.
1//! ResetEvent is a thread-safe bool which can be set to true/false ("set"/"unset").
2//! It can also block threads until the "bool" is set with cancellation via timed waits.
3//! ResetEvent can be statically initialized and is at most `@sizeOf(u64)` large.
84
9const ResetEvent = @This();
105const std = @import("../std.zig");
116const builtin = @import("builtin");
12const testing = std.testing;
13const assert = std.debug.assert;
14const c = std.c;
15const os = std.os;
16const time = std.time;
17
18impl: Impl,
7const ResetEvent = @This();
198
20pub const Impl = if (builtin.single_threaded)
21 std.Thread.StaticResetEvent.DebugEvent
22else if (builtin.target.isDarwin())
23 DarwinEvent
24else if (std.Thread.use_pthreads)
25 PosixEvent
26else
27 std.Thread.StaticResetEvent.AtomicEvent;
9const os = std.os;
10const assert = std.debug.assert;
11const testing = std.testing;
12const Atomic = std.atomic.Atomic;
13const Futex = std.Thread.Futex;
2814
29pub const InitError = error{SystemResources};
15impl: Impl = .{},
3016
31/// After `init`, it is legal to call any other function.
32pub fn init(ev: *ResetEvent) InitError!void {
33 return ev.impl.init();
17/// Returns if the ResetEvent was set().
18/// Once reset() is called, this returns false until the next set().
19/// The memory accesses before the set() can be said to happen before isSet() returns true.
20pub fn isSet(self: *const ResetEvent) bool {
21 return self.impl.isSet();
3422}
3523
36/// This function is not thread-safe.
37/// After `deinit`, the only legal function to call is `init`.
38pub fn deinit(ev: *ResetEvent) void {
39 return ev.impl.deinit();
24/// Block's the callers thread until the ResetEvent is set().
25/// This is effectively a more efficient version of `while (!isSet()) {}`.
26/// The memory accesses before the set() can be said to happen before wait() returns.
27pub 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 };
4031}
4132
42/// Sets the event if not already set and wakes up all the threads waiting on
43/// the event. It is safe to call `set` multiple times before calling `wait`.
44/// However it is illegal to call `set` after `wait` is called until the event
45/// is `reset`. This function is thread-safe.
46pub fn set(ev: *ResetEvent) void {
47 return ev.impl.set();
33/// Block's the callers thread until the ResetEvent is set(), or until the corresponding timeout expires.
34/// If the timeout expires before the ResetEvent is set, `error.Timeout` is returned.
35/// This is effectively a more efficient version of `while (!isSet()) {}`.
36/// The memory accesses before the set() can be said to happen before timedWait() returns without error.
37pub fn timedWait(self: *ResetEvent, timeout_ns: u64) error{Timeout}!void {
38 return self.impl.wait(timeout_ns);
4839}
4940
50/// Resets the event to its original, unset state.
51/// This function is *not* thread-safe. It is equivalent to calling
52/// `deinit` followed by `init` but without the possibility of failure.
53pub fn reset(ev: *ResetEvent) void {
54 return ev.impl.reset();
41/// Marks the ResetEvent as "set" and unblocks any threads in `wait()` or `timedWait()` to observe the new state.
42/// The ResetEvent says "set" until reset() is called, making future set() calls do nothing semantically.
43/// The memory accesses before set() can be said to happen before isSet() returns true or wait()/timedWait() return successfully.
44pub fn set(self: *ResetEvent) void {
45 self.impl.set();
5546}
5647
57/// Wait for the event to be set by blocking the current thread.
58/// Thread-safe. No spurious wakeups.
59/// Upon return from `wait`, the only functions available to be called
60/// in `ResetEvent` are `reset` and `deinit`.
61pub fn wait(ev: *ResetEvent) void {
62 return ev.impl.wait();
48/// Unmarks the ResetEvent from its "set" state if set() was called previously.
49/// It is undefined behavior is reset() is called while threads are blocked in wait() or timedWait().
50/// Concurrent calls to set(), isSet() and reset() are allowed.
51pub fn reset(self: *ResetEvent) void {
52 self.impl.reset();
6353}
6454
65pub const TimedWaitResult = enum { event_set, timed_out };
66
67/// Wait for the event to be set by blocking the current thread.
68/// A timeout in nanoseconds can be provided as a hint for how
69/// long the thread should block on the unset event before returning
70/// `TimedWaitResult.timed_out`.
71/// Thread-safe. No precision of timing is guaranteed.
72/// Upon return from `wait`, the only functions available to be called
73/// in `ResetEvent` are `reset` and `deinit`.
74pub fn timedWait(ev: *ResetEvent, timeout_ns: u64) TimedWaitResult {
75 return ev.impl.timedWait(timeout_ns);
76}
55const Impl = if (builtin.single_threaded)
56 SingleThreadedImpl
57else
58 FutexImpl;
7759
78/// Apple has decided to not support POSIX semaphores, so we go with a
79/// different approach using Grand Central Dispatch. This API is exposed
80/// by libSystem so it is guaranteed to be available on all Darwin platforms.
81pub const DarwinEvent = struct {
82 sem: c.dispatch_semaphore_t = undefined,
60const SingleThreadedImpl = struct {
61 is_set: bool = false,
8362
84 pub fn init(ev: *DarwinEvent) !void {
85 ev.* = .{
86 .sem = c.dispatch_semaphore_create(0) orelse return error.SystemResources,
87 };
63 fn isSet(self: *const Impl) bool {
64 return self.is_set;
8865 }
8966
90 pub fn deinit(ev: *DarwinEvent) void {
91 c.dispatch_release(ev.sem);
92 ev.* = undefined;
93 }
67 fn wait(self: *Impl, timeout: ?u64) error{Timeout}!void {
68 if (self.isSet()) {
69 return;
70 }
9471
95 pub fn set(ev: *DarwinEvent) void {
96 // Empirically this returns the numerical value of the semaphore.
97 _ = c.dispatch_semaphore_signal(ev.sem);
98 }
72 // There are no other threads to wake us up.
73 // So if we wait without a timeout we would never wake up.
74 const timeout_ns = timeout orelse {
75 unreachable; // deadlock detected
76 };
9977
100 pub fn wait(ev: *DarwinEvent) void {
101 assert(c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_FOREVER) == 0);
78 std.time.sleep(timeout_ns);
79 return error.Timeout;
10280 }
10381
104 pub fn timedWait(ev: *DarwinEvent, timeout_ns: u64) TimedWaitResult {
105 const t = c.dispatch_time(c.DISPATCH_TIME_NOW, @intCast(i64, timeout_ns));
106 if (c.dispatch_semaphore_wait(ev.sem, t) != 0) {
107 return .timed_out;
108 } else {
109 return .event_set;
110 }
82 fn set(self: *Impl) void {
83 self.is_set = true;
11184 }
11285
113 pub fn reset(ev: *DarwinEvent) void {
114 // Keep calling until the semaphore goes back down to 0.
115 while (c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_NOW) == 0) {}
86 fn reset(self: *Impl) void {
87 self.is_set = false;
11688 }
11789};
11890
119/// POSIX semaphores must be initialized at runtime because they are allowed to
120/// be implemented as file descriptors, in which case initialization would require
121/// a syscall to open the fd.
122pub const PosixEvent = struct {
123 sem: c.sem_t = undefined,
91const FutexImpl = struct {
92 state: Atomic(u32) = Atomic(u32).init(unset),
12493
125 pub fn init(ev: *PosixEvent) !void {
126 switch (c.getErrno(c.sem_init(&ev.sem, 0, 0))) {
127 .SUCCESS => return,
128 else => return error.SystemResources,
129 }
130 }
94 const unset = 0;
95 const waiting = 1;
96 const is_set = 2;
13197
132 pub fn deinit(ev: *PosixEvent) void {
133 assert(c.sem_destroy(&ev.sem) == 0);
134 ev.* = undefined;
98 fn isSet(self: *const Impl) bool {
99 // Acquire barrier ensures memory accesses before set() happen before we return true.
100 return self.state.load(.Acquire) == is_set;
135101 }
136102
137 pub fn set(ev: *PosixEvent) void {
138 assert(c.sem_post(&ev.sem) == 0);
103 fn wait(self: *Impl, timeout: ?u64) error{Timeout}!void {
104 // Outline the slow path to allow isSet() to be inlined
105 if (!self.isSet()) {
106 return self.waitUntilSet(timeout);
107 }
139108 }
140109
141 pub fn wait(ev: *PosixEvent) void {
142 while (true) {
143 switch (c.getErrno(c.sem_wait(&ev.sem))) {
144 .SUCCESS => return,
145 .INTR => continue,
146 .INVAL => unreachable,
147 else => unreachable,
148 }
110 fn waitUntilSet(self: *Impl, timeout: ?u64) error{Timeout}!void {
111 @setCold(true);
112
113 // Try to set the state from `unset` to `waiting` to indicate
114 // to the set() thread that others are blocked on the ResetEvent.
115 // We avoid using any strict barriers until the end when we know the ResetEvent is set.
116 var state = self.state.load(.Monotonic);
117 if (state == unset) {
118 state = self.state.compareAndSwap(state, waiting, .Monotonic, .Monotonic) orelse waiting;
149119 }
150 }
151120
152 pub fn timedWait(ev: *PosixEvent, timeout_ns: u64) TimedWaitResult {
153 var ts: os.timespec = undefined;
154 var timeout_abs = timeout_ns;
155 os.clock_gettime(os.CLOCK.REALTIME, &ts) catch return .timed_out;
156 timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s;
157 timeout_abs += @intCast(u64, ts.tv_nsec);
158 ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s));
159 ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s));
160 while (true) {
161 switch (c.getErrno(c.sem_timedwait(&ev.sem, &ts))) {
162 .SUCCESS => return .event_set,
163 .INTR => continue,
164 .INVAL => unreachable,
165 .TIMEDOUT => return .timed_out,
166 else => unreachable,
121 // Wait until the ResetEvent is set since the state is waiting.
122 if (state == waiting) {
123 var futex_deadline = Futex.Deadline.init(timeout);
124 while (true) {
125 const wait_result = futex_deadline.wait(&self.state, waiting);
126
127 // Check if the ResetEvent was set before possibly reporting error.Timeout below.
128 state = self.state.load(.Monotonic);
129 if (state != waiting) {
130 break;
131 }
132
133 try wait_result;
167134 }
168135 }
136
137 // Acquire barrier ensures memory accesses before set() happen before we return.
138 assert(state == is_set);
139 self.state.fence(.Acquire);
169140 }
170141
171 pub fn reset(ev: *PosixEvent) void {
172 while (true) {
173 switch (c.getErrno(c.sem_trywait(&ev.sem))) {
174 .SUCCESS => continue, // Need to make it go to zero.
175 .INTR => continue,
176 .INVAL => unreachable,
177 .AGAIN => return, // The semaphore currently has the value zero.
178 else => unreachable,
179 }
142 fn set(self: *Impl) void {
143 // Quick check if the ResetEvent is already set before doing the atomic swap below.
144 // set() could be getting called quite often and multiple threads calling swap() increases contention unnecessarily.
145 if (self.state.load(.Monotonic) == is_set) {
146 return;
147 }
148
149 // Mark the ResetEvent as set and unblock all waiters waiting on it if any.
150 // Release barrier ensures memory accesses before set() happen before the ResetEvent is observed to be "set".
151 if (self.state.swap(is_set, .Release) == waiting) {
152 Futex.wake(&self.state, std.math.maxInt(u32));
180153 }
181154 }
155
156 fn reset(self: *Impl) void {
157 self.state.store(unset, .Monotonic);
158 }
182159};
183160
184test "basic usage" {
185 var event: ResetEvent = undefined;
186 try event.init();
187 defer event.deinit();
161test "ResetEvent - smoke test" {
162 // make sure the event is unset
163 var event = ResetEvent{};
164 try testing.expectEqual(false, event.isSet());
188165
189 // test event setting
166 // make sure the event gets set
190167 event.set();
168 try testing.expectEqual(true, event.isSet());
191169
192 // test event resetting
170 // make sure the event gets unset again
193171 event.reset();
172 try testing.expectEqual(false, event.isSet());
194173
195 // test event waiting (non-blocking)
196 event.set();
197 event.wait();
198 event.reset();
174 // waits should timeout as there's no other thread to set the event
175 try testing.expectError(error.Timeout, event.timedWait(0));
176 try testing.expectError(error.Timeout, event.timedWait(std.time.ns_per_ms));
199177
178 // set the event again and make sure waits complete
200179 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}
202184
203 // test cross-thread signaling
204 if (builtin.single_threaded)
205 return;
185test "ResetEvent - signaling" {
186 // This test requires spawning threads
187 if (builtin.single_threaded) {
188 return error.SkipZigTest;
189 }
206190
207191 const Context = struct {
208 const Self = @This();
209
210 value: u128,
211 in: ResetEvent,
212 out: ResetEvent,
213
214 fn init(self: *Self) !void {
215 self.* = .{
216 .value = 0,
217 .in = undefined,
218 .out = undefined,
219 };
220 try self.in.init();
221 try self.out.init();
222 }
192 in: ResetEvent = .{},
193 out: ResetEvent = .{},
194 value: usize = 0,
195
196 fn input(self: *@This()) !void {
197 // wait for the value to become 1
198 self.in.wait();
199 self.in.reset();
200 try testing.expectEqual(self.value, 1);
201
202 // bump the value and wake up output()
203 self.value = 2;
204 self.out.set();
223205
224 fn deinit(self: *Self) void {
225 self.in.deinit();
226 self.out.deinit();
227 self.* = undefined;
206 // wait for output to receive 2, bump the value and wake us up with 3
207 self.in.wait();
208 self.in.reset();
209 try testing.expectEqual(self.value, 3);
210
211 // bump the value and wake up output() for it to see 4
212 self.value = 4;
213 self.out.set();
228214 }
229215
230 fn sender(self: *Self) !void {
231 // update value and signal input
232 try testing.expect(self.value == 0);
216 fn output(self: *@This()) !void {
217 // start with 0 and bump the value for input to see 1
218 try testing.expectEqual(self.value, 0);
233219 self.value = 1;
234220 self.in.set();
235221
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
237223 self.out.wait();
238 try testing.expect(self.value == 2);
224 self.out.reset();
225 try testing.expectEqual(self.value, 2);
239226
240 // update value and signal final input
227 // bump the value to 3 for input to see (rhymes)
241228 self.value = 3;
242229 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);
243235 }
236 };
244237
245 fn receiver(self: *Self) !void {
246 // wait for sender to update value and signal input
247 self.in.wait();
248 try testing.expect(self.value == 1);
238 var ctx = Context{};
249239
250 // update value and signal output
251 self.in.reset();
252 self.value = 2;
253 self.out.set();
240 const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx});
241 defer thread.join();
254242
255 // wait for sender to update value and signal final input
256 self.in.wait();
257 try testing.expect(self.value == 3);
258 }
243 try ctx.input();
244}
259245
260 fn sleeper(self: *Self) void {
261 self.in.set();
262 time.sleep(time.ns_per_ms * 2);
263 self.value = 5;
264 self.out.set();
246test "ResetEvent - broadcast" {
247 // This test requires spawning threads
248 if (builtin.single_threaded) {
249 return error.SkipZigTest;
250 }
251
252 const num_threads = 10;
253 const Barrier = struct {
254 event: ResetEvent = .{},
255 counter: Atomic(usize) = Atomic(usize).init(num_threads),
256
257 fn wait(self: *@This()) void {
258 if (self.counter.fetchSub(1, .AcqRel) == 1) {
259 self.event.set();
260 }
265261 }
262 };
266263
267 fn timedWaiter(self: *Self) !void {
268 self.in.wait();
269 try testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us));
270 try self.out.timedWait(time.ns_per_ms * 100);
271 try testing.expect(self.value == 5);
264 const Context = struct {
265 start_barrier: Barrier = .{},
266 finish_barrier: Barrier = .{},
267
268 fn run(self: *@This()) void {
269 self.start_barrier.wait();
270 self.finish_barrier.wait();
272271 }
273272 };
274273
275 var context: Context = undefined;
276 try context.init();
277 defer context.deinit();
278 const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context});
279 defer receiver.join();
280 try context.sender();
281
282 if (false) {
283 // I have now observed this fail on macOS, Windows, and Linux.
284 // https://github.com/ziglang/zig/issues/7009
285 var timed = Context.init();
286 defer timed.deinit();
287 const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed});
288 defer sleeper.join();
289 try timed.timedWaiter();
290 }
274 var ctx = Context{};
275 var threads: [num_threads - 1]std.Thread = undefined;
276
277 for (threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx});
278 defer for (threads) |t| t.join();
279
280 ctx.run();
291281}
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
10const std = @import("../std.zig");
11const builtin = @import("builtin");
12const StaticResetEvent = @This();
13const assert = std.debug.assert;
14const os = std.os;
15const time = std.time;
16const linux = std.os.linux;
17const windows = std.os.windows;
18const testing = std.testing;
19
20impl: Impl = .{},
21
22pub const Impl = if (builtin.single_threaded)
23 DebugEvent
24else
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.
31pub 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`.
39pub 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.
46pub fn reset(ev: *StaticResetEvent) void {
47 return ev.impl.reset();
48}
49
50pub 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`.
59pub 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.
65pub 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
116pub 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
308test "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 {
1414 return .{ .value = value };
1515 }
1616
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
1764 /// Non-atomically load from the atomic value without synchronization.
1865 /// 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 {
2067 return self.value;
2168 }
2269
2370 /// Non-atomically store to the atomic value without synchronization.
2471 /// 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 {
2673 self.value = value;
2774 }
2875
29 pub fn load(self: *const Self, comptime ordering: Ordering) T {
76 pub inline fn load(self: *const Self, comptime ordering: Ordering) T {
3077 return switch (ordering) {
3178 .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on atomic stores"),
3279 .Release => @compileError(@tagName(ordering) ++ " is only allowed on atomic stores"),
......@@ -34,7 +81,7 @@ pub fn Atomic(comptime T: type) type {
3481 };
3582 }
3683
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 {
3885 return switch (ordering) {
3986 .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Acquire) ++ " which is only allowed on atomic loads"),
4087 .Acquire => @compileError(@tagName(ordering) ++ " is only allowed on atomic loads"),
......@@ -189,21 +236,21 @@ pub fn Atomic(comptime T: type) type {
189236 .Set => asm volatile ("lock btsw %[bit], %[ptr]"
190237 // LLVM doesn't support u1 flag register return values
191238 : [result] "={@ccc}" (-> u8),
192 : [ptr] "*p" (&self.value),
239 : [ptr] "*m" (&self.value),
193240 [bit] "X" (@as(T, bit)),
194241 : "cc", "memory"
195242 ),
196243 .Reset => asm volatile ("lock btrw %[bit], %[ptr]"
197244 // LLVM doesn't support u1 flag register return values
198245 : [result] "={@ccc}" (-> u8),
199 : [ptr] "*p" (&self.value),
246 : [ptr] "*m" (&self.value),
200247 [bit] "X" (@as(T, bit)),
201248 : "cc", "memory"
202249 ),
203250 .Toggle => asm volatile ("lock btcw %[bit], %[ptr]"
204251 // LLVM doesn't support u1 flag register return values
205252 : [result] "={@ccc}" (-> u8),
206 : [ptr] "*p" (&self.value),
253 : [ptr] "*m" (&self.value),
207254 [bit] "X" (@as(T, bit)),
208255 : "cc", "memory"
209256 ),
......@@ -212,21 +259,21 @@ pub fn Atomic(comptime T: type) type {
212259 .Set => asm volatile ("lock btsl %[bit], %[ptr]"
213260 // LLVM doesn't support u1 flag register return values
214261 : [result] "={@ccc}" (-> u8),
215 : [ptr] "*p" (&self.value),
262 : [ptr] "*m" (&self.value),
216263 [bit] "X" (@as(T, bit)),
217264 : "cc", "memory"
218265 ),
219266 .Reset => asm volatile ("lock btrl %[bit], %[ptr]"
220267 // LLVM doesn't support u1 flag register return values
221268 : [result] "={@ccc}" (-> u8),
222 : [ptr] "*p" (&self.value),
269 : [ptr] "*m" (&self.value),
223270 [bit] "X" (@as(T, bit)),
224271 : "cc", "memory"
225272 ),
226273 .Toggle => asm volatile ("lock btcl %[bit], %[ptr]"
227274 // LLVM doesn't support u1 flag register return values
228275 : [result] "={@ccc}" (-> u8),
229 : [ptr] "*p" (&self.value),
276 : [ptr] "*m" (&self.value),
230277 [bit] "X" (@as(T, bit)),
231278 : "cc", "memory"
232279 ),
......@@ -235,21 +282,21 @@ pub fn Atomic(comptime T: type) type {
235282 .Set => asm volatile ("lock btsq %[bit], %[ptr]"
236283 // LLVM doesn't support u1 flag register return values
237284 : [result] "={@ccc}" (-> u8),
238 : [ptr] "*p" (&self.value),
285 : [ptr] "*m" (&self.value),
239286 [bit] "X" (@as(T, bit)),
240287 : "cc", "memory"
241288 ),
242289 .Reset => asm volatile ("lock btrq %[bit], %[ptr]"
243290 // LLVM doesn't support u1 flag register return values
244291 : [result] "={@ccc}" (-> u8),
245 : [ptr] "*p" (&self.value),
292 : [ptr] "*m" (&self.value),
246293 [bit] "X" (@as(T, bit)),
247294 : "cc", "memory"
248295 ),
249296 .Toggle => asm volatile ("lock btcq %[bit], %[ptr]"
250297 // LLVM doesn't support u1 flag register return values
251298 : [result] "={@ccc}" (-> u8),
252 : [ptr] "*p" (&self.value),
299 : [ptr] "*m" (&self.value),
253300 [bit] "X" (@as(T, bit)),
254301 : "cc", "memory"
255302 ),
......@@ -266,6 +313,13 @@ pub fn Atomic(comptime T: type) type {
266313 };
267314}
268315
316test "Atomic.fence" {
317 inline for (.{ .Acquire, .Release, .AcqRel, .SeqCst }) |ordering| {
318 var x = Atomic(usize).init(0);
319 x.fence(ordering);
320 }
321}
322
269323fn atomicIntTypes() []const type {
270324 comptime var bytes = 1;
271325 comptime var types: []const type = &[_]type{};
lib/std/debug.zig+5-5
......@@ -292,7 +292,7 @@ pub fn panicExtra(
292292
293293/// Non-zero whenever the program triggered a panic.
294294/// The counter is incremented/decremented atomically.
295var panicking: u8 = 0;
295var panicking = std.atomic.Atomic(u8).init(0);
296296
297297// Locked to avoid interleaving panic messages from multiple threads.
298298var panic_mutex = std.Thread.Mutex{};
......@@ -316,7 +316,7 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
316316 0 => {
317317 panic_stage = 1;
318318
319 _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst);
319 _ = panicking.fetchAdd(1, .SeqCst);
320320
321321 // Make sure to release the mutex when done
322322 {
......@@ -337,13 +337,13 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
337337 dumpCurrentStackTrace(first_trace_addr);
338338 }
339339
340 if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) {
340 if (panicking.fetchSub(1, .SeqCst) != 1) {
341341 // Another thread is panicking, wait for the last one to finish
342342 // and call abort()
343343
344344 // Sleep forever without hammering the CPU
345 var event: std.Thread.StaticResetEvent = .{};
346 event.wait();
345 var futex = std.atomic.Atomic(u32).init(0);
346 while (true) std.Thread.Futex.wait(&futex, 0);
347347 unreachable;
348348 }
349349 },
lib/std/event/loop.zig+19-14
......@@ -8,6 +8,7 @@ const os = std.os;
88const windows = os.windows;
99const maxInt = std.math.maxInt;
1010const Thread = std.Thread;
11const Atomic = std.atomic.Atomic;
1112
1213const is_windows = builtin.os.tag == .windows;
1314
......@@ -168,11 +169,9 @@ pub const Loop = struct {
168169 .fs_end_request = .{ .data = .{ .msg = .end, .finish = .NoAction } },
169170 .fs_queue = std.atomic.Queue(Request).init(),
170171 .fs_thread = undefined,
171 .fs_thread_wakeup = undefined,
172 .fs_thread_wakeup = .{},
172173 .delay_queue = undefined,
173174 };
174 try self.fs_thread_wakeup.init();
175 errdefer self.fs_thread_wakeup.deinit();
176175 errdefer self.arena.deinit();
177176
178177 // We need at least one of these in case the fs thread wants to use onNextTick
......@@ -202,7 +201,6 @@ pub const Loop = struct {
202201
203202 pub fn deinit(self: *Loop) void {
204203 self.deinitOsData();
205 self.fs_thread_wakeup.deinit();
206204 self.arena.deinit();
207205 self.* = undefined;
208206 }
......@@ -723,9 +721,7 @@ pub const Loop = struct {
723721 extra_thread.join();
724722 }
725723
726 @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst);
727 self.delay_queue.event.set();
728 self.delay_queue.thread.join();
724 self.delay_queue.deinit();
729725 }
730726
731727 /// Runs the provided function asynchronously. The function's frame is allocated
......@@ -851,8 +847,8 @@ pub const Loop = struct {
851847 timer: std.time.Timer,
852848 waiters: Waiters,
853849 thread: std.Thread,
854 event: std.Thread.AutoResetEvent,
855 is_running: bool,
850 event: std.Thread.ResetEvent,
851 is_running: Atomic(bool),
856852
857853 /// Initialize the delay queue by spawning the timer thread
858854 /// and starting any timer resources.
......@@ -862,11 +858,19 @@ pub const Loop = struct {
862858 .waiters = DelayQueue.Waiters{
863859 .entries = std.atomic.Queue(anyframe).init(),
864860 },
865 .event = std.Thread.AutoResetEvent{},
866 .is_running = true,
867 // Must be last so that it can read the other state, such as `is_running`.
868 .thread = try std.Thread.spawn(.{}, DelayQueue.run, .{self}),
861 .thread = undefined,
862 .event = .{},
863 .is_running = Atomic(bool).init(true),
869864 };
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();
870874 }
871875
872876 /// Entry point for the timer thread
......@@ -874,7 +878,8 @@ pub const Loop = struct {
874878 fn run(self: *DelayQueue) void {
875879 const loop = @fieldParentPtr(Loop, "delay_queue", self);
876880
877 while (@atomicLoad(bool, &self.is_running, .SeqCst)) {
881 while (self.is_running.load(.SeqCst)) {
882 self.event.reset();
878883 const now = self.timer.read();
879884
880885 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" {
917917 try testing.expectError(error.WouldBlock, file2);
918918}
919919
920test "open file with exclusive lock twice, make sure it waits" {
920test "open file with exclusive lock twice, make sure second lock waits" {
921921 if (builtin.single_threaded) return error.SkipZigTest;
922922
923923 if (std.io.is_async) {
......@@ -934,30 +934,33 @@ test "open file with exclusive lock twice, make sure it waits" {
934934 errdefer file.close();
935935
936936 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();
938939 const file1 = try dir.createFile(filename, .{ .lock = .Exclusive });
939 defer file1.close();
940 evt.set();
940
941 locked.set();
942 file1.close();
941943 }
942944 };
943945
944 var evt: std.Thread.ResetEvent = undefined;
945 try evt.init();
946 defer evt.deinit();
946 var started = std.Thread.ResetEvent{};
947 var locked = std.Thread.ResetEvent{};
947948
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 });
949954 defer t.join();
950955
951 const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms;
952 // Make sure we've slept enough.
953 var timer = try std.time.Timer.start();
954 while (true) {
955 std.time.sleep(SLEEP_TIMEOUT_NS);
956 if (timer.read() >= SLEEP_TIMEOUT_NS) break;
957 }
956 // Wait for the spawned thread to start trying to acquire the exclusive file lock.
957 // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
958 started.wait();
959 try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
960
961 // Release the file lock which should unlock the thread to lock it and set the locked event.
958962 file.close();
959 // No timeout to avoid failures on heavily loaded systems.
960 evt.wait();
963 locked.wait();
961964}
962965
963966test "open file with exclusive nonblocking lock twice (absolute paths)" {
src/Compilation.zig+4-13
......@@ -163,8 +163,8 @@ emit_llvm_bc: ?EmitLoc,
163163emit_analysis: ?EmitLoc,
164164emit_docs: ?EmitLoc,
165165
166work_queue_wait_group: WaitGroup,
167astgen_wait_group: WaitGroup,
166work_queue_wait_group: WaitGroup = .{},
167astgen_wait_group: WaitGroup = .{},
168168
169169/// Exported symbol names. This is only for when the target is wasm.
170170/// 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 {
16741674 .test_evented_io = options.test_evented_io,
16751675 .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs,
16761676 .debug_compile_errors = options.debug_compile_errors,
1677 .work_queue_wait_group = undefined,
1678 .astgen_wait_group = undefined,
16791677 };
16801678 break :comp comp;
16811679 };
16821680 errdefer comp.destroy();
16831681
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
16901682 // Add a `CObject` for each `c_source_files`.
16911683 try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len);
16921684 for (options.c_source_files) |c_source_file| {
......@@ -1894,9 +1886,6 @@ pub fn destroy(self: *Compilation) void {
18941886 self.cache_parent.manifest_dir.close();
18951887 if (self.owned_link_dir) |*dir| dir.close();
18961888
1897 self.work_queue_wait_group.deinit();
1898 self.astgen_wait_group.deinit();
1899
19001889 for (self.export_symbol_names.items) |symbol_name| {
19011890 gpa.free(symbol_name);
19021891 }
......@@ -4701,6 +4690,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
47014690 \\pub const link_libcpp = {};
47024691 \\pub const have_error_return_tracing = {};
47034692 \\pub const valgrind_support = {};
4693 \\pub const sanitize_thread = {};
47044694 \\pub const position_independent_code = {};
47054695 \\pub const position_independent_executable = {};
47064696 \\pub const strip_debug_info = {};
......@@ -4713,6 +4703,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
47134703 comp.bin_file.options.link_libcpp,
47144704 comp.bin_file.options.error_return_tracing,
47154705 comp.bin_file.options.valgrind,
4706 comp.bin_file.options.tsan,
47164707 comp.bin_file.options.pic,
47174708 comp.bin_file.options.pie,
47184709 comp.bin_file.options.strip,
src/ThreadPool.zig+64-75
......@@ -3,13 +3,12 @@ const builtin = @import("builtin");
33const ThreadPool = @This();
44
55mutex: std.Thread.Mutex = .{},
6cond: std.Thread.Condition = .{},
7run_queue: RunQueue = .{},
68is_running: bool = true,
79allocator: std.mem.Allocator,
8workers: []Worker,
9run_queue: RunQueue = .{},
10idle_queue: IdleQueue = .{},
10threads: []std.Thread,
1111
12const IdleQueue = std.SinglyLinkedList(std.Thread.ResetEvent);
1312const RunQueue = std.SinglyLinkedList(Runnable);
1413const Runnable = struct {
1514 runFn: RunProto,
......@@ -20,89 +19,52 @@ const RunProto = switch (builtin.zig_backend) {
2019 else => *const fn (*Runnable) void,
2120};
2221
23const 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
5822pub fn init(self: *ThreadPool, allocator: std.mem.Allocator) !void {
5923 self.* = .{
6024 .allocator = allocator,
61 .workers = &[_]Worker{},
25 .threads = &[_]std.Thread{},
6226 };
63 if (builtin.single_threaded)
64 return;
6527
66 const worker_count = std.math.max(1, std.Thread.getCpuCount() catch 1);
67 self.workers = try allocator.alloc(Worker, worker_count);
68 errdefer allocator.free(self.workers);
28 if (builtin.single_threaded) {
29 return;
30 }
6931
70 var worker_index: usize = 0;
71 errdefer self.destroyWorkers(worker_index);
72 while (worker_index < worker_count) : (worker_index += 1) {
73 const worker = &self.workers[worker_index];
74 worker.pool = self;
32 const thread_count = std.math.max(1, std.Thread.getCpuCount() catch 1);
33 self.threads = try allocator.alloc(std.Thread, thread_count);
34 errdefer allocator.free(self.threads);
7535
76 // Each worker requires its ResetEvent to be pre-initialized.
77 try worker.idle_node.data.init();
78 errdefer worker.idle_node.data.deinit();
36 // kill and join any threads we spawned previously on error.
37 var spawned: usize = 0;
38 errdefer self.join(spawned);
7939
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;
8143 }
8244}
8345
84fn destroyWorkers(self: *ThreadPool, spawned: usize) void {
85 if (builtin.single_threaded)
86 return;
87
88 for (self.workers[0..spawned]) |*worker| {
89 worker.thread.join();
90 worker.idle_node.data.deinit();
91 }
46pub fn deinit(self: *ThreadPool) void {
47 self.join(self.threads.len); // kill and join all threads.
48 self.* = undefined;
9249}
9350
94pub fn deinit(self: *ThreadPool) void {
51fn join(self: *ThreadPool, spawned: usize) void {
9552 {
9653 self.mutex.lock();
9754 defer self.mutex.unlock();
9855
56 // ensure future worker threads exit the dequeue loop
9957 self.is_running = false;
100 while (self.idle_queue.popFirst()) |idle_node|
101 idle_node.data.set();
10258 }
10359
104 self.destroyWorkers(self.workers.len);
105 self.allocator.free(self.workers);
60 // wake up any sleeping threads (this can be done outside the mutex)
61 // then wait for all the threads we know are spawned to complete.
62 self.cond.broadcast();
63 for (self.threads[0..spawned]) |thread| {
64 thread.join();
65 }
66
67 self.allocator.free(self.threads);
10668}
10769
10870pub 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 {
12284 const closure = @fieldParentPtr(@This(), "run_node", run_node);
12385 @call(.{}, func, closure.arguments);
12486
87 // The thread pool's allocator is protected by the mutex.
12588 const mutex = &closure.pool.mutex;
12689 mutex.lock();
12790 defer mutex.unlock();
91
12892 closure.pool.allocator.destroy(closure);
12993 }
13094 };
13195
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
113fn worker(self: *ThreadPool) void {
132114 self.mutex.lock();
133115 defer self.mutex.unlock();
134116
135 const closure = try self.allocator.create(Closure);
136 closure.* = .{
137 .arguments = args,
138 .pool = self,
139 };
117 while (true) {
118 while (self.run_queue.popFirst()) |run_node| {
119 // Temporarily unlock the mutex in order to execute the run_node
120 self.mutex.unlock();
121 defer self.mutex.lock();
140122
141 self.run_queue.prepend(&closure.run_node);
123 const runFn = run_node.data.runFn;
124 runFn(&run_node.data);
125 }
142126
143 if (self.idle_queue.popFirst()) |idle_node|
144 idle_node.data.set();
127 // Stop executing instead of waiting if the thread pool is no longer running.
128 if (self.is_running) {
129 self.cond.wait(&self.mutex);
130 } else {
131 break;
132 }
133 }
145134}
src/WaitGroup.zig+16-33
......@@ -1,56 +1,39 @@
11const std = @import("std");
2const Atomic = std.atomic.Atomic;
3const assert = std.debug.assert;
24const WaitGroup = @This();
35
4mutex: std.Thread.Mutex = .{},
5counter: usize = 0,
6event: std.Thread.ResetEvent,
7
8pub fn init(self: *WaitGroup) !void {
9 self.* = .{
10 .mutex = .{},
11 .counter = 0,
12 .event = undefined,
13 };
14 try self.event.init();
15}
6const is_waiting: usize = 1 << 0;
7const one_pending: usize = 1 << 1;
168
17pub fn deinit(self: *WaitGroup) void {
18 self.event.deinit();
19 self.* = undefined;
20}
9state: Atomic(usize) = Atomic(usize).init(0),
10event: std.Thread.ResetEvent = .{},
2111
2212pub fn start(self: *WaitGroup) void {
23 self.mutex.lock();
24 defer self.mutex.unlock();
25
26 self.counter += 1;
13 const state = self.state.fetchAdd(one_pending, .Monotonic);
14 assert((state / one_pending) < (std.math.maxInt(usize) / one_pending));
2715}
2816
2917pub fn finish(self: *WaitGroup) void {
30 self.mutex.lock();
31 defer self.mutex.unlock();
18 const state = self.state.fetchSub(one_pending, .Release);
19 assert((state / one_pending) > 0);
3220
33 self.counter -= 1;
34
35 if (self.counter == 0) {
21 if (state == (one_pending | is_waiting)) {
22 self.state.fence(.Acquire);
3623 self.event.set();
3724 }
3825}
3926
4027pub fn wait(self: *WaitGroup) void {
41 while (true) {
42 self.mutex.lock();
43
44 if (self.counter == 0) {
45 self.mutex.unlock();
46 return;
47 }
28 var state = self.state.fetchAdd(is_waiting, .Acquire);
29 assert(state & is_waiting == 0);
4830
49 self.mutex.unlock();
31 if ((state / one_pending) > 0) {
5032 self.event.wait();
5133 }
5234}
5335
5436pub fn reset(self: *WaitGroup) void {
37 self.state.store(0, .Monotonic);
5538 self.event.reset();
5639}
src/crash_report.zig+6-5
......@@ -362,7 +362,7 @@ const PanicSwitch = struct {
362362 /// Updated atomically before taking the panic_mutex.
363363 /// In recoverable cases, the program will not abort
364364 /// until all panicking threads have dumped their traces.
365 var panicking: u8 = 0;
365 var panicking = std.atomic.Atomic(u8).init(0);
366366
367367 // Locked to avoid interleaving panic messages from multiple threads.
368368 var panic_mutex = std.Thread.Mutex{};
......@@ -430,7 +430,7 @@ const PanicSwitch = struct {
430430 };
431431 state.* = new_state;
432432
433 _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst);
433 _ = panicking.fetchAdd(1, .SeqCst);
434434
435435 state.recover_stage = .release_ref_count;
436436
......@@ -512,13 +512,14 @@ const PanicSwitch = struct {
512512 noinline fn releaseRefCount(state: *volatile PanicState) noreturn {
513513 state.recover_stage = .abort;
514514
515 if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) {
515 if (panicking.fetchSub(1, .SeqCst) != 1) {
516516 // Another thread is panicking, wait for the last one to finish
517517 // and call abort()
518518
519519 // Sleep forever without hammering the CPU
520 var event: std.Thread.StaticResetEvent = .{};
521 event.wait();
520 var futex = std.atomic.Atomic(u32).init(0);
521 while (true) std.Thread.Futex.wait(&futex, 0);
522
522523 // This should be unreachable, recurse into recoverAbort.
523524 @panic("event.wait() returned");
524525 }
src/stage1/codegen.cpp+1
......@@ -9993,6 +9993,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
99939993 buf_appendf(contents, "pub const link_libcpp = %s;\n", bool_to_str(g->link_libcpp));
99949994 buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing));
99959995 buf_appendf(contents, "pub const valgrind_support = false;\n");
9996 buf_appendf(contents, "pub const sanitize_thread = false;\n");
99969997 buf_appendf(contents, "pub const position_independent_code = %s;\n", bool_to_str(g->have_pic));
99979998 buf_appendf(contents, "pub const position_independent_executable = %s;\n", bool_to_str(g->have_pie));
99989999 buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols));