authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-24 00:15:33-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-12-24 00:15:33-08:00
log0fd68f49e2eabb866ea1d21c4657c2a1d3c8ce53
tree6217c0d05293c9c1bbb7a8d3c2e24409f0a040c5
parent577b57784ae70a85f5f4fef37e749687d0e9b6b0
parent87e4f7376aa384183a793cb42498ed0ff06222d5
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7519 from ziglang/more-pthreads-integration

More pthreads integration

18 files changed, 937 insertions(+), 635 deletions(-)

CMakeLists.txt+3-3
......@@ -410,11 +410,12 @@ set(ZIG_STAGE2_SOURCES
410410 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/bits.zig"
411411 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"
412412 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
413 "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
414 "${CMAKE_SOURCE_DIR}/lib/std/ResetEvent.zig"
415 "${CMAKE_SOURCE_DIR}/lib/std/StaticResetEvent.zig"
413416 "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"
414417 "${CMAKE_SOURCE_DIR}/lib/std/process.zig"
415 "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
416418 "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"
417 "${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig"
418419 "${CMAKE_SOURCE_DIR}/lib/std/sort.zig"
419420 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt.zig"
420421 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/addXf3.zig"
......@@ -512,7 +513,6 @@ set(ZIG_STAGE2_SOURCES
512513 "${CMAKE_SOURCE_DIR}/src/Cache.zig"
513514 "${CMAKE_SOURCE_DIR}/src/Compilation.zig"
514515 "${CMAKE_SOURCE_DIR}/src/DepTokenizer.zig"
515 "${CMAKE_SOURCE_DIR}/src/Event.zig"
516516 "${CMAKE_SOURCE_DIR}/src/Module.zig"
517517 "${CMAKE_SOURCE_DIR}/src/Package.zig"
518518 "${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
lib/std/Progress.zig+3
......@@ -160,6 +160,9 @@ pub fn maybeRefresh(self: *Progress) void {
160160 if (now < self.initial_delay_ns) return;
161161 const held = self.update_lock.tryAcquire() orelse return;
162162 defer held.release();
163 // TODO I have observed this to happen sometimes. I think we need to follow Rust's
164 // lead and guarantee monotonically increasing times in the std lib itself.
165 if (now < self.prev_refresh_timestamp) return;
163166 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
164167 return self.refreshWithHeldLock();
165168}
lib/std/ResetEvent.zig created+297
......@@ -0,0 +1,297 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7//! A thread-safe resource which supports blocking until signaled.
8//! This API is for kernel threads, not evented I/O.
9//! This API requires being initialized at runtime, and initialization
10//! can fail. Once initialized, the core operations cannot fail.
11//! If you need an abstraction that cannot fail to be initialized, see
12//! `std.StaticResetEvent`. However if you can handle initialization failure,
13//! it is preferred to use `ResetEvent`.
14
15const ResetEvent = @This();
16const std = @import("std.zig");
17const builtin = std.builtin;
18const testing = std.testing;
19const assert = std.debug.assert;
20const c = std.c;
21const os = std.os;
22const time = std.time;
23
24impl: Impl,
25
26pub const Impl = if (builtin.single_threaded)
27 std.StaticResetEvent.DebugEvent
28else if (std.Target.current.isDarwin())
29 DarwinEvent
30else if (std.Thread.use_pthreads)
31 PosixEvent
32else
33 std.StaticResetEvent.AtomicEvent;
34
35pub const InitError = error{SystemResources};
36
37/// After `init`, it is legal to call any other function.
38pub fn init(ev: *ResetEvent) InitError!void {
39 return ev.impl.init();
40}
41
42/// This function is not thread-safe.
43/// After `deinit`, the only legal function to call is `init`.
44pub fn deinit(ev: *ResetEvent) void {
45 return ev.impl.deinit();
46}
47
48/// Sets the event if not already set and wakes up all the threads waiting on
49/// the event. It is safe to call `set` multiple times before calling `wait`.
50/// However it is illegal to call `set` after `wait` is called until the event
51/// is `reset`. This function is thread-safe.
52pub fn set(ev: *ResetEvent) void {
53 return ev.impl.set();
54}
55
56/// Resets the event to its original, unset state.
57/// This function is *not* thread-safe. It is equivalent to calling
58/// `deinit` followed by `init` but without the possibility of failure.
59pub fn reset(ev: *ResetEvent) void {
60 return ev.impl.reset();
61}
62
63/// Wait for the event to be set by blocking the current thread.
64/// Thread-safe. No spurious wakeups.
65/// Upon return from `wait`, the only functions available to be called
66/// in `ResetEvent` are `reset` and `deinit`.
67pub fn wait(ev: *ResetEvent) void {
68 return ev.impl.wait();
69}
70
71pub const TimedWaitResult = enum { event_set, timed_out };
72
73/// Wait for the event to be set by blocking the current thread.
74/// A timeout in nanoseconds can be provided as a hint for how
75/// long the thread should block on the unset event before returning
76/// `TimedWaitResult.timed_out`.
77/// Thread-safe. No precision of timing is guaranteed.
78/// Upon return from `wait`, the only functions available to be called
79/// in `ResetEvent` are `reset` and `deinit`.
80pub fn timedWait(ev: *ResetEvent, timeout_ns: u64) TimedWaitResult {
81 return ev.impl.timedWait(timeout_ns);
82}
83
84/// Apple has decided to not support POSIX semaphores, so we go with a
85/// different approach using Grand Central Dispatch. This API is exposed
86/// by libSystem so it is guaranteed to be available on all Darwin platforms.
87pub const DarwinEvent = struct {
88 sem: c.dispatch_semaphore_t = undefined,
89
90 pub fn init(ev: *DarwinEvent) !void {
91 ev.* = .{
92 .sem = c.dispatch_semaphore_create(0) orelse return error.SystemResources,
93 };
94 }
95
96 pub fn deinit(ev: *DarwinEvent) void {
97 c.dispatch_release(ev.sem);
98 ev.* = undefined;
99 }
100
101 pub fn set(ev: *DarwinEvent) void {
102 // Empirically this returns the numerical value of the semaphore.
103 _ = c.dispatch_semaphore_signal(ev.sem);
104 }
105
106 pub fn wait(ev: *DarwinEvent) void {
107 assert(c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_FOREVER) == 0);
108 }
109
110 pub fn timedWait(ev: *DarwinEvent, timeout_ns: u64) TimedWaitResult {
111 const t = c.dispatch_time(c.DISPATCH_TIME_NOW, @intCast(i64, timeout_ns));
112 if (c.dispatch_semaphore_wait(ev.sem, t) != 0) {
113 return .timed_out;
114 } else {
115 return .event_set;
116 }
117 }
118
119 pub fn reset(ev: *DarwinEvent) void {
120 // Keep calling until the semaphore goes back down to 0.
121 while (c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_NOW) == 0) {}
122 }
123};
124
125/// POSIX semaphores must be initialized at runtime because they are allowed to
126/// be implemented as file descriptors, in which case initialization would require
127/// a syscall to open the fd.
128pub const PosixEvent = struct {
129 sem: c.sem_t = undefined,
130
131 pub fn init(ev: *PosixEvent) !void {
132 switch (c.getErrno(c.sem_init(&ev.sem, 0, 0))) {
133 0 => return,
134 else => return error.SystemResources,
135 }
136 }
137
138 pub fn deinit(ev: *PosixEvent) void {
139 assert(c.sem_destroy(&ev.sem) == 0);
140 ev.* = undefined;
141 }
142
143 pub fn set(ev: *PosixEvent) void {
144 assert(c.sem_post(&ev.sem) == 0);
145 }
146
147 pub fn wait(ev: *PosixEvent) void {
148 while (true) {
149 switch (c.getErrno(c.sem_wait(&ev.sem))) {
150 0 => return,
151 c.EINTR => continue,
152 c.EINVAL => unreachable,
153 else => unreachable,
154 }
155 }
156 }
157
158 pub fn timedWait(ev: *PosixEvent, timeout_ns: u64) TimedWaitResult {
159 var ts: os.timespec = undefined;
160 var timeout_abs = timeout_ns;
161 os.clock_gettime(os.CLOCK_REALTIME, &ts) catch return .timed_out;
162 timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s;
163 timeout_abs += @intCast(u64, ts.tv_nsec);
164 ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s));
165 ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s));
166 while (true) {
167 switch (c.getErrno(c.sem_timedwait(&ev.sem, &ts))) {
168 0 => return .event_set,
169 c.EINTR => continue,
170 c.EINVAL => unreachable,
171 c.ETIMEDOUT => return .timed_out,
172 else => unreachable,
173 }
174 }
175 }
176
177 pub fn reset(ev: *PosixEvent) void {
178 while (true) {
179 switch (c.getErrno(c.sem_trywait(&ev.sem))) {
180 0 => continue, // Need to make it go to zero.
181 c.EINTR => continue,
182 c.EINVAL => unreachable,
183 c.EAGAIN => return, // The semaphore currently has the value zero.
184 else => unreachable,
185 }
186 }
187 }
188};
189
190test "basic usage" {
191 var event: ResetEvent = undefined;
192 try event.init();
193 defer event.deinit();
194
195 // test event setting
196 event.set();
197
198 // test event resetting
199 event.reset();
200
201 // test event waiting (non-blocking)
202 event.set();
203 event.wait();
204 event.reset();
205
206 event.set();
207 testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1));
208
209 // test cross-thread signaling
210 if (builtin.single_threaded)
211 return;
212
213 const Context = struct {
214 const Self = @This();
215
216 value: u128,
217 in: ResetEvent,
218 out: ResetEvent,
219
220 fn init(self: *Self) !void {
221 self.* = .{
222 .value = 0,
223 .in = undefined,
224 .out = undefined,
225 };
226 try self.in.init();
227 try self.out.init();
228 }
229
230 fn deinit(self: *Self) void {
231 self.in.deinit();
232 self.out.deinit();
233 self.* = undefined;
234 }
235
236 fn sender(self: *Self) void {
237 // update value and signal input
238 testing.expect(self.value == 0);
239 self.value = 1;
240 self.in.set();
241
242 // wait for receiver to update value and signal output
243 self.out.wait();
244 testing.expect(self.value == 2);
245
246 // update value and signal final input
247 self.value = 3;
248 self.in.set();
249 }
250
251 fn receiver(self: *Self) void {
252 // wait for sender to update value and signal input
253 self.in.wait();
254 assert(self.value == 1);
255
256 // update value and signal output
257 self.in.reset();
258 self.value = 2;
259 self.out.set();
260
261 // wait for sender to update value and signal final input
262 self.in.wait();
263 assert(self.value == 3);
264 }
265
266 fn sleeper(self: *Self) void {
267 self.in.set();
268 time.sleep(time.ns_per_ms * 2);
269 self.value = 5;
270 self.out.set();
271 }
272
273 fn timedWaiter(self: *Self) !void {
274 self.in.wait();
275 testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us));
276 try self.out.timedWait(time.ns_per_ms * 100);
277 testing.expect(self.value == 5);
278 }
279 };
280
281 var context: Context = undefined;
282 try context.init();
283 defer context.deinit();
284 const receiver = try std.Thread.spawn(&context, Context.receiver);
285 defer receiver.wait();
286 context.sender();
287
288 if (false) {
289 // I have now observed this fail on macOS, Windows, and Linux.
290 // https://github.com/ziglang/zig/issues/7009
291 var timed = Context.init();
292 defer timed.deinit();
293 const sleeper = try std.Thread.spawn(&timed, Context.sleeper);
294 defer sleeper.wait();
295 try timed.timedWaiter();
296 }
297}
lib/std/StaticResetEvent.zig created+396
......@@ -0,0 +1,396 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7//! A thread-safe resource which supports blocking until signaled.
8//! This API is for kernel threads, not evented I/O.
9//! This API is statically initializable. It cannot fail to be initialized
10//! and it requires no deinitialization. The downside is that it may not
11//! integrate as cleanly into other synchronization APIs, or, in a worst case,
12//! may be forced to fall back on spin locking. As a rule of thumb, prefer
13//! to use `std.ResetEvent` when possible, and use `StaticResetEvent` when
14//! the logic needs stronger API guarantees.
15
16const std = @import("std.zig");
17const StaticResetEvent = @This();
18const SpinLock = std.SpinLock;
19const assert = std.debug.assert;
20const os = std.os;
21const time = std.time;
22const linux = std.os.linux;
23const windows = std.os.windows;
24const testing = std.testing;
25
26impl: Impl = .{},
27
28pub const Impl = if (std.builtin.single_threaded)
29 DebugEvent
30else
31 AtomicEvent;
32
33/// Sets the event if not already set and wakes up all the threads waiting on
34/// the event. It is safe to call `set` multiple times before calling `wait`.
35/// However it is illegal to call `set` after `wait` is called until the event
36/// is `reset`. This function is thread-safe.
37pub fn set(ev: *StaticResetEvent) void {
38 return ev.impl.set();
39}
40
41/// Wait for the event to be set by blocking the current thread.
42/// Thread-safe. No spurious wakeups.
43/// Upon return from `wait`, the only function available to be called
44/// in `StaticResetEvent` is `reset`.
45pub fn wait(ev: *StaticResetEvent) void {
46 return ev.impl.wait();
47}
48
49/// Resets the event to its original, unset state.
50/// This function is *not* thread-safe. It is equivalent to calling
51/// `deinit` followed by `init` but without the possibility of failure.
52pub fn reset(ev: *StaticResetEvent) void {
53 return ev.impl.reset();
54}
55
56pub const TimedWaitResult = std.ResetEvent.TimedWaitResult;
57
58/// Wait for the event to be set by blocking the current thread.
59/// A timeout in nanoseconds can be provided as a hint for how
60/// long the thread should block on the unset event before returning
61/// `TimedWaitResult.timed_out`.
62/// Thread-safe. No precision of timing is guaranteed.
63/// Upon return from `timedWait`, the only function available to be called
64/// in `StaticResetEvent` is `reset`.
65pub fn timedWait(ev: *StaticResetEvent, timeout_ns: u64) TimedWaitResult {
66 return ev.impl.timedWait(timeout_ns);
67}
68
69/// For single-threaded builds, we use this to detect deadlocks.
70/// In unsafe modes this ends up being no-ops.
71pub const DebugEvent = struct {
72 state: State = State.unset,
73
74 const State = enum {
75 unset,
76 set,
77 waited,
78 };
79
80 /// This function is provided so that this type can be re-used inside
81 /// `std.ResetEvent`.
82 pub fn init(ev: *DebugEvent) void {
83 ev.* = .{};
84 }
85
86 /// This function is provided so that this type can be re-used inside
87 /// `std.ResetEvent`.
88 pub fn deinit(ev: *DebugEvent) void {
89 ev.* = undefined;
90 }
91
92 pub fn set(ev: *DebugEvent) void {
93 switch (ev.state) {
94 .unset => ev.state = .set,
95 .set => {},
96 .waited => unreachable, // Not allowed to call `set` until `reset`.
97 }
98 }
99
100 pub fn wait(ev: *DebugEvent) void {
101 switch (ev.state) {
102 .unset => unreachable, // Deadlock detected.
103 .set => return,
104 .waited => unreachable, // Not allowed to call `wait` until `reset`.
105 }
106 }
107
108 pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult {
109 switch (ev.state) {
110 .unset => return .timed_out,
111 .set => return .event_set,
112 .waited => unreachable, // Not allowed to call `wait` until `reset`.
113 }
114 }
115
116 pub fn reset(ev: *DebugEvent) void {
117 ev.state = .unset;
118 }
119};
120
121pub const AtomicEvent = struct {
122 waiters: u32 = 0,
123
124 const WAKE = 1 << 0;
125 const WAIT = 1 << 1;
126
127 /// This function is provided so that this type can be re-used inside
128 /// `std.ResetEvent`.
129 pub fn init(ev: *AtomicEvent) void {
130 ev.* = .{};
131 }
132
133 /// This function is provided so that this type can be re-used inside
134 /// `std.ResetEvent`.
135 pub fn deinit(ev: *AtomicEvent) void {
136 ev.* = undefined;
137 }
138
139 pub fn set(ev: *AtomicEvent) void {
140 const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release);
141 if (waiters >= WAIT) {
142 return Futex.wake(&ev.waiters, waiters >> 1);
143 }
144 }
145
146 pub fn wait(ev: *AtomicEvent) void {
147 switch (ev.timedWait(null)) {
148 .timed_out => unreachable,
149 .event_set => return,
150 }
151 }
152
153 pub fn timedWait(ev: *AtomicEvent, timeout: ?u64) TimedWaitResult {
154 var waiters = @atomicLoad(u32, &ev.waiters, .Acquire);
155 while (waiters != WAKE) {
156 waiters = @cmpxchgWeak(u32, &ev.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse {
157 if (Futex.wait(&ev.waiters, timeout)) |_| {
158 return .event_set;
159 } else |_| {
160 return .timed_out;
161 }
162 };
163 }
164 return .event_set;
165 }
166
167 pub fn reset(ev: *AtomicEvent) void {
168 @atomicStore(u32, &ev.waiters, 0, .Monotonic);
169 }
170
171 pub const Futex = switch (std.Target.current.os.tag) {
172 .windows => WindowsFutex,
173 .linux => LinuxFutex,
174 else => SpinFutex,
175 };
176
177 pub const SpinFutex = struct {
178 fn wake(waiters: *u32, wake_count: u32) void {}
179
180 fn wait(waiters: *u32, timeout: ?u64) !void {
181 var timer: time.Timer = undefined;
182 if (timeout != null)
183 timer = time.Timer.start() catch return error.TimedOut;
184
185 while (@atomicLoad(u32, waiters, .Acquire) != WAKE) {
186 SpinLock.yield();
187 if (timeout) |timeout_ns| {
188 if (timer.read() >= timeout_ns)
189 return error.TimedOut;
190 }
191 }
192 }
193 };
194
195 pub const LinuxFutex = struct {
196 fn wake(waiters: *u32, wake_count: u32) void {
197 const waiting = std.math.maxInt(i32); // wake_count
198 const ptr = @ptrCast(*const i32, waiters);
199 const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting);
200 assert(linux.getErrno(rc) == 0);
201 }
202
203 fn wait(waiters: *u32, timeout: ?u64) !void {
204 var ts: linux.timespec = undefined;
205 var ts_ptr: ?*linux.timespec = null;
206 if (timeout) |timeout_ns| {
207 ts_ptr = &ts;
208 ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s);
209 ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s);
210 }
211
212 while (true) {
213 const waiting = @atomicLoad(u32, waiters, .Acquire);
214 if (waiting == WAKE)
215 return;
216 const expected = @intCast(i32, waiting);
217 const ptr = @ptrCast(*const i32, waiters);
218 const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr);
219 switch (linux.getErrno(rc)) {
220 0 => continue,
221 os.ETIMEDOUT => return error.TimedOut,
222 os.EINTR => continue,
223 os.EAGAIN => return,
224 else => unreachable,
225 }
226 }
227 }
228 };
229
230 pub const WindowsFutex = struct {
231 pub fn wake(waiters: *u32, wake_count: u32) void {
232 const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count);
233 const key = @ptrCast(*const c_void, waiters);
234
235 var waiting = wake_count;
236 while (waiting != 0) : (waiting -= 1) {
237 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
238 assert(rc == .SUCCESS);
239 }
240 }
241
242 pub fn wait(waiters: *u32, timeout: ?u64) !void {
243 const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout);
244 const key = @ptrCast(*const c_void, waiters);
245
246 // NT uses timeouts in units of 100ns with negative value being relative
247 var timeout_ptr: ?*windows.LARGE_INTEGER = null;
248 var timeout_value: windows.LARGE_INTEGER = undefined;
249 if (timeout) |timeout_ns| {
250 timeout_ptr = &timeout_value;
251 timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100);
252 }
253
254 // NtWaitForKeyedEvent doesnt have spurious wake-ups
255 var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr);
256 switch (rc) {
257 .TIMEOUT => {
258 // update the wait count to signal that we're not waiting anymore.
259 // if the .set() thread already observed that we are, perform a
260 // matching NtWaitForKeyedEvent so that the .set() thread doesn't
261 // deadlock trying to run NtReleaseKeyedEvent above.
262 var waiting = @atomicLoad(u32, waiters, .Monotonic);
263 while (true) {
264 if (waiting == WAKE) {
265 rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
266 assert(rc == .WAIT_0);
267 break;
268 } else {
269 waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break;
270 continue;
271 }
272 }
273 return error.TimedOut;
274 },
275 .WAIT_0 => {},
276 else => unreachable,
277 }
278 }
279
280 var event_handle: usize = EMPTY;
281 const EMPTY = ~@as(usize, 0);
282 const LOADING = EMPTY - 1;
283
284 pub fn getEventHandle() ?windows.HANDLE {
285 var handle = @atomicLoad(usize, &event_handle, .Monotonic);
286 while (true) {
287 switch (handle) {
288 EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse {
289 const handle_ptr = @ptrCast(*windows.HANDLE, &handle);
290 const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE;
291 if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS)
292 handle = 0;
293 @atomicStore(usize, &event_handle, handle, .Monotonic);
294 return @intToPtr(?windows.HANDLE, handle);
295 },
296 LOADING => {
297 SpinLock.yield();
298 handle = @atomicLoad(usize, &event_handle, .Monotonic);
299 },
300 else => {
301 return @intToPtr(?windows.HANDLE, handle);
302 },
303 }
304 }
305 }
306 };
307};
308
309test "basic usage" {
310 var event = StaticResetEvent{};
311
312 // test event setting
313 event.set();
314
315 // test event resetting
316 event.reset();
317
318 // test event waiting (non-blocking)
319 event.set();
320 event.wait();
321 event.reset();
322
323 event.set();
324 testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1));
325
326 // test cross-thread signaling
327 if (std.builtin.single_threaded)
328 return;
329
330 const Context = struct {
331 const Self = @This();
332
333 value: u128 = 0,
334 in: StaticResetEvent = .{},
335 out: StaticResetEvent = .{},
336
337 fn sender(self: *Self) void {
338 // update value and signal input
339 testing.expect(self.value == 0);
340 self.value = 1;
341 self.in.set();
342
343 // wait for receiver to update value and signal output
344 self.out.wait();
345 testing.expect(self.value == 2);
346
347 // update value and signal final input
348 self.value = 3;
349 self.in.set();
350 }
351
352 fn receiver(self: *Self) void {
353 // wait for sender to update value and signal input
354 self.in.wait();
355 assert(self.value == 1);
356
357 // update value and signal output
358 self.in.reset();
359 self.value = 2;
360 self.out.set();
361
362 // wait for sender to update value and signal final input
363 self.in.wait();
364 assert(self.value == 3);
365 }
366
367 fn sleeper(self: *Self) void {
368 self.in.set();
369 time.sleep(time.ns_per_ms * 2);
370 self.value = 5;
371 self.out.set();
372 }
373
374 fn timedWaiter(self: *Self) !void {
375 self.in.wait();
376 testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us));
377 try self.out.timedWait(time.ns_per_ms * 100);
378 testing.expect(self.value == 5);
379 }
380 };
381
382 var context = Context{};
383 const receiver = try std.Thread.spawn(&context, Context.receiver);
384 defer receiver.wait();
385 context.sender();
386
387 if (false) {
388 // I have now observed this fail on macOS, Windows, and Linux.
389 // https://github.com/ziglang/zig/issues/7009
390 var timed = Context.init();
391 defer timed.deinit();
392 const sleeper = try std.Thread.spawn(&timed, Context.sleeper);
393 defer sleeper.wait();
394 try timed.timedWaiter();
395 }
396}
lib/std/auto_reset_event.zig+21-22
......@@ -7,14 +7,15 @@ const std = @import("std.zig");
77const builtin = @import("builtin");
88const testing = std.testing;
99const assert = std.debug.assert;
10const StaticResetEvent = std.StaticResetEvent;
1011
11/// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`.
12/// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like).
12/// Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`.
13/// Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like).
1314pub const AutoResetEvent = struct {
1415 /// AutoResetEvent has 3 possible states:
1516 /// - UNSET: the AutoResetEvent is currently unset
1617 /// - SET: the AutoResetEvent was notified before a wait() was called
17 /// - <std.ResetEvent pointer>: there is an active waiter waiting for a notification.
18 /// - <StaticResetEvent pointer>: there is an active waiter waiting for a notification.
1819 ///
1920 /// When attempting to wait:
2021 /// if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
......@@ -25,20 +26,20 @@ pub const AutoResetEvent = struct {
2526 /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
2627 ///
2728 /// This ensures that the event is automatically reset after a wait() has been issued
28 /// and avoids the race condition when using std.ResetEvent in the following scenario:
29 /// thread 1 | thread 2
30 /// std.ResetEvent.wait() |
31 /// | std.ResetEvent.set()
32 /// | std.ResetEvent.set()
33 /// std.ResetEvent.reset() |
34 /// std.ResetEvent.wait() | (missed the second .set() notification above)
29 /// and avoids the race condition when using StaticResetEvent in the following scenario:
30 /// thread 1 | thread 2
31 /// StaticResetEvent.wait() |
32 /// | StaticResetEvent.set()
33 /// | StaticResetEvent.set()
34 /// StaticResetEvent.reset() |
35 /// StaticResetEvent.wait() | (missed the second .set() notification above)
3536 state: usize = UNSET,
3637
3738 const UNSET = 0;
3839 const SET = 1;
3940
40 /// the minimum alignment for the `*std.ResetEvent` created by wait*()
41 const event_align = std.math.max(@alignOf(std.ResetEvent), 2);
41 /// the minimum alignment for the `*StaticResetEvent` created by wait*()
42 const event_align = std.math.max(@alignOf(StaticResetEvent), 2);
4243
4344 pub fn wait(self: *AutoResetEvent) void {
4445 self.waitFor(null) catch unreachable;
......@@ -49,12 +50,9 @@ pub const AutoResetEvent = struct {
4950 }
5051
5152 fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void {
52 // lazily initialized std.ResetEvent
53 var reset_event: std.ResetEvent align(event_align) = undefined;
53 // lazily initialized StaticResetEvent
54 var reset_event: StaticResetEvent align(event_align) = undefined;
5455 var has_reset_event = false;
55 defer if (has_reset_event) {
56 reset_event.deinit();
57 };
5856
5957 var state = @atomicLoad(usize, &self.state, .SeqCst);
6058 while (true) {
......@@ -72,7 +70,7 @@ pub const AutoResetEvent = struct {
7270 // lazily initialize the ResetEvent if it hasn't been already
7371 if (!has_reset_event) {
7472 has_reset_event = true;
75 reset_event = std.ResetEvent.init();
73 reset_event = .{};
7674 }
7775
7876 // Since the AutoResetEvent currently isnt set,
......@@ -97,9 +95,10 @@ pub const AutoResetEvent = struct {
9795 };
9896
9997 // wait with a timeout and return if signalled via set()
100 if (reset_event.timedWait(timeout_ns)) |_| {
101 return;
102 } else |timed_out| {}
98 switch (reset_event.timedWait(timeout_ns)) {
99 .event_set => return,
100 .timed_out => {},
101 }
103102
104103 // If we timed out, we need to transition the AutoResetEvent back to UNSET.
105104 // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent.
......@@ -164,7 +163,7 @@ pub const AutoResetEvent = struct {
164163 continue;
165164 }
166165
167 const reset_event = @intToPtr(*align(event_align) std.ResetEvent, state);
166 const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state);
168167 reset_event.set();
169168 return;
170169 }
lib/std/c.zig+8
......@@ -270,6 +270,13 @@ pub extern "c" fn pthread_atfork(
270270 parent: ?fn () callconv(.C) void,
271271 child: ?fn () callconv(.C) void,
272272) c_int;
273pub extern "c" fn sem_init(sem: *sem_t, pshared: c_int, value: c_uint) c_int;
274pub extern "c" fn sem_destroy(sem: *sem_t) c_int;
275pub extern "c" fn sem_post(sem: *sem_t) c_int;
276pub extern "c" fn sem_wait(sem: *sem_t) c_int;
277pub extern "c" fn sem_trywait(sem: *sem_t) c_int;
278pub extern "c" fn sem_timedwait(sem: *sem_t, abs_timeout: *const timespec) c_int;
279pub extern "c" fn sem_getvalue(sem: *sem_t, sval: *c_int) c_int;
273280
274281pub extern "c" fn kqueue() c_int;
275282pub extern "c" fn kevent(
......@@ -316,6 +323,7 @@ pub extern "c" fn dn_expand(
316323pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{};
317324pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) c_int;
318325pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) c_int;
326pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) c_int;
319327pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int;
320328
321329pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{};
lib/std/c/darwin.zig+13
......@@ -177,6 +177,7 @@ pub const pthread_cond_t = extern struct {
177177 __sig: c_long = 0x3CB0B1BB,
178178 __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__,
179179};
180pub const sem_t = c_int;
180181const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40;
181182const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24;
182183
......@@ -186,3 +187,15 @@ pub const pthread_attr_t = extern struct {
186187};
187188
188189pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;
190
191// Grand Central Dispatch is exposed by libSystem.
192pub const dispatch_semaphore_t = *opaque{};
193pub const dispatch_time_t = u64;
194pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0);
195pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0);
196pub extern "c" fn dispatch_semaphore_create(value: isize) ?dispatch_semaphore_t;
197pub extern "c" fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) isize;
198pub extern "c" fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) isize;
199
200pub extern "c" fn dispatch_release(object: *c_void) void;
201pub extern "c" fn dispatch_time(when: dispatch_time_t, delta: i64) dispatch_time_t;
lib/std/c/freebsd.zig+9
......@@ -47,6 +47,15 @@ pub const pthread_attr_t = extern struct {
4747 __align: c_long,
4848};
4949
50pub const sem_t = extern struct {
51 _magic: u32,
52 _kern: extern struct {
53 _count: u32,
54 _flags: u32,
55 },
56 _padding: u32,
57};
58
5059pub const EAI = extern enum(c_int) {
5160 /// address family for hostname not supported
5261 ADDRFAMILY = 1,
lib/std/c/linux.zig+5
......@@ -123,6 +123,10 @@ pub const pthread_mutex_t = extern struct {
123123pub const pthread_cond_t = extern struct {
124124 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
125125};
126pub const sem_t = extern struct {
127 __size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)),
128};
129
126130const __SIZEOF_PTHREAD_COND_T = 48;
127131const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) {
128132 .musl, .musleabi, .musleabihf => if (@sizeOf(usize) == 8) 40 else 24,
......@@ -134,6 +138,7 @@ const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch
134138 },
135139 else => unreachable,
136140};
141const __SIZEOF_SEM_T = 4 * @sizeOf(usize);
137142
138143pub const RTLD_LAZY = 1;
139144pub const RTLD_NOW = 2;
lib/std/debug.zig+1-2
......@@ -274,9 +274,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
274274 // and call abort()
275275
276276 // Sleep forever without hammering the CPU
277 var event = std.ResetEvent.init();
277 var event: std.StaticResetEvent = .{};
278278 event.wait();
279
280279 unreachable;
281280 }
282281 },
lib/std/fs/test.zig+2-3
......@@ -758,7 +758,8 @@ test "open file with exclusive lock twice, make sure it waits" {
758758 }
759759 };
760760
761 var evt = std.ResetEvent.init();
761 var evt: std.ResetEvent = undefined;
762 try evt.init();
762763 defer evt.deinit();
763764
764765 const t = try std.Thread.spawn(S.C{ .dir = &tmp.dir, .evt = &evt }, S.checkFn);
......@@ -771,8 +772,6 @@ test "open file with exclusive lock twice, make sure it waits" {
771772 std.time.sleep(SLEEP_TIMEOUT_NS);
772773 if (timer.read() >= SLEEP_TIMEOUT_NS) break;
773774 }
774 // Check that createFile is still waiting for the lock to be released.
775 testing.expect(!evt.isSet());
776775 file.close();
777776 // No timeout to avoid failures on heavily loaded systems.
778777 evt.wait();
lib/std/mutex.zig+57-8
......@@ -10,7 +10,7 @@ const assert = std.debug.assert;
1010const windows = os.windows;
1111const testing = std.testing;
1212const SpinLock = std.SpinLock;
13const ResetEvent = std.ResetEvent;
13const StaticResetEvent = std.StaticResetEvent;
1414
1515/// Lock may be held only once. If the same thread tries to acquire
1616/// the same mutex twice, it deadlocks. This type supports static
......@@ -37,6 +37,8 @@ pub const Mutex = if (builtin.single_threaded)
3737 Dummy
3838else if (builtin.os.tag == .windows)
3939 WindowsMutex
40else if (std.Thread.use_pthreads)
41 PthreadMutex
4042else if (builtin.link_libc or builtin.os.tag == .linux)
4143 // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs
4244 struct {
......@@ -52,7 +54,7 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
5254
5355 const Node = struct {
5456 next: ?*Node,
55 event: ResetEvent,
57 event: StaticResetEvent,
5658 };
5759
5860 pub fn tryAcquire(self: *Mutex) ?Held {
......@@ -88,11 +90,12 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
8890 state = @atomicLoad(usize, &self.state, .Monotonic);
8991 }
9092
91 // create the ResetEvent node on the stack
93 // create the StaticResetEvent node on the stack
9294 // (faster than threadlocal on platforms like OSX)
93 var node: Node = undefined;
94 node.event = ResetEvent.init();
95 defer node.event.deinit();
95 var node: Node = .{
96 .next = undefined,
97 .event = .{},
98 };
9699
97100 // we've spun too long, try and add our node to the LIFO queue.
98101 // if the mutex becomes available in the process, try and grab it instead.
......@@ -166,6 +169,52 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
166169else
167170 SpinLock;
168171
172pub const PthreadMutex = struct {
173 pthread_mutex: std.c.pthread_mutex_t = init,
174
175 pub const Held = struct {
176 mutex: *PthreadMutex,
177
178 pub fn release(self: Held) void {
179 switch (std.c.pthread_mutex_unlock(&self.mutex.pthread_mutex)) {
180 0 => return,
181 std.c.EINVAL => unreachable,
182 std.c.EAGAIN => unreachable,
183 std.c.EPERM => unreachable,
184 else => unreachable,
185 }
186 }
187 };
188
189 /// Create a new mutex in unlocked state.
190 pub const init = std.c.PTHREAD_MUTEX_INITIALIZER;
191
192 /// Try to acquire the mutex without blocking. Returns null if
193 /// the mutex is unavailable. Otherwise returns Held. Call
194 /// release on Held.
195 pub fn tryAcquire(self: *PthreadMutex) ?Held {
196 if (std.c.pthread_mutex_trylock(&self.pthread_mutex) == 0) {
197 return Held{ .mutex = self };
198 } else {
199 return null;
200 }
201 }
202
203 /// Acquire the mutex. Will deadlock if the mutex is already
204 /// held by the calling thread.
205 pub fn acquire(self: *PthreadMutex) Held {
206 switch (std.c.pthread_mutex_lock(&self.pthread_mutex)) {
207 0 => return Held{ .mutex = self },
208 std.c.EINVAL => unreachable,
209 std.c.EBUSY => unreachable,
210 std.c.EAGAIN => unreachable,
211 std.c.EDEADLK => unreachable,
212 std.c.EPERM => unreachable,
213 else => unreachable,
214 }
215 }
216};
217
169218/// This has the sematics as `Mutex`, however it does not actually do any
170219/// synchronization. Operations are safety-checked no-ops.
171220pub const Dummy = struct {
......@@ -236,7 +285,7 @@ const WindowsMutex = struct {
236285 fn acquireSlow(self: *WindowsMutex) Held {
237286 // try to use NT keyed events for blocking, falling back to spinlock if unavailable
238287 @setCold(true);
239 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning();
288 const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return self.acquireSpinning();
240289 const key = @ptrCast(*const c_void, &self.state.waiters);
241290
242291 while (true) : (SpinLock.loopHint(1)) {
......@@ -264,7 +313,7 @@ const WindowsMutex = struct {
264313 pub fn release(self: Held) void {
265314 // unlock without a rmw/cmpxchg instruction
266315 @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release);
267 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return;
316 const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return;
268317 const key = @ptrCast(*const c_void, &self.mutex.state.waiters);
269318
270319 while (true) : (SpinLock.loopHint(1)) {
lib/std/reset_event.zig deleted-468
......@@ -1,468 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std.zig");
7const builtin = @import("builtin");
8const testing = std.testing;
9const SpinLock = std.SpinLock;
10const assert = std.debug.assert;
11const c = std.c;
12const os = std.os;
13const time = std.time;
14const linux = os.linux;
15const windows = os.windows;
16
17/// A resource object which supports blocking until signaled.
18/// Once finished, the `deinit()` method should be called for correctness.
19pub const ResetEvent = struct {
20 os_event: OsEvent,
21
22 pub const OsEvent = if (builtin.single_threaded)
23 DebugEvent
24 else if (builtin.link_libc and builtin.os.tag != .windows and builtin.os.tag != .linux)
25 PosixEvent
26 else
27 AtomicEvent;
28
29 pub fn init() ResetEvent {
30 return ResetEvent{ .os_event = OsEvent.init() };
31 }
32
33 pub fn deinit(self: *ResetEvent) void {
34 self.os_event.deinit();
35 }
36
37 /// Returns whether or not the event is currenetly set
38 pub fn isSet(self: *ResetEvent) bool {
39 return self.os_event.isSet();
40 }
41
42 /// Sets the event if not already set and
43 /// wakes up all the threads waiting on the event.
44 pub fn set(self: *ResetEvent) void {
45 return self.os_event.set();
46 }
47
48 /// Resets the event to its original, unset state.
49 pub fn reset(self: *ResetEvent) void {
50 return self.os_event.reset();
51 }
52
53 /// Wait for the event to be set by blocking the current thread.
54 pub fn wait(self: *ResetEvent) void {
55 return self.os_event.wait(null) catch unreachable;
56 }
57
58 /// Wait for the event to be set by blocking the current thread.
59 /// A timeout in nanoseconds can be provided as a hint for how
60 /// long the thread should block on the unset event before throwing error.TimedOut.
61 pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void {
62 return self.os_event.wait(timeout_ns);
63 }
64};
65
66const DebugEvent = struct {
67 is_set: bool,
68
69 fn init() DebugEvent {
70 return DebugEvent{ .is_set = false };
71 }
72
73 fn deinit(self: *DebugEvent) void {
74 self.* = undefined;
75 }
76
77 fn isSet(self: *DebugEvent) bool {
78 return self.is_set;
79 }
80
81 fn reset(self: *DebugEvent) void {
82 self.is_set = false;
83 }
84
85 fn set(self: *DebugEvent) void {
86 self.is_set = true;
87 }
88
89 fn wait(self: *DebugEvent, timeout: ?u64) !void {
90 if (self.is_set)
91 return;
92 if (timeout != null)
93 return error.TimedOut;
94 @panic("deadlock detected");
95 }
96};
97
98const PosixEvent = struct {
99 is_set: bool,
100 cond: c.pthread_cond_t,
101 mutex: c.pthread_mutex_t,
102
103 fn init() PosixEvent {
104 return PosixEvent{
105 .is_set = false,
106 .cond = c.PTHREAD_COND_INITIALIZER,
107 .mutex = c.PTHREAD_MUTEX_INITIALIZER,
108 };
109 }
110
111 fn deinit(self: *PosixEvent) void {
112 // on dragonfly or openbsd, *destroy() functions can return EINVAL
113 // for statically initialized pthread structures
114 const err = if (builtin.os.tag == .dragonfly or builtin.os.tag == .openbsd)
115 os.EINVAL
116 else
117 0;
118
119 const retm = c.pthread_mutex_destroy(&self.mutex);
120 assert(retm == 0 or retm == err);
121 const retc = c.pthread_cond_destroy(&self.cond);
122 assert(retc == 0 or retc == err);
123 }
124
125 fn isSet(self: *PosixEvent) bool {
126 assert(c.pthread_mutex_lock(&self.mutex) == 0);
127 defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
128
129 return self.is_set;
130 }
131
132 fn reset(self: *PosixEvent) void {
133 assert(c.pthread_mutex_lock(&self.mutex) == 0);
134 defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
135
136 self.is_set = false;
137 }
138
139 fn set(self: *PosixEvent) void {
140 assert(c.pthread_mutex_lock(&self.mutex) == 0);
141 defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
142
143 if (!self.is_set) {
144 self.is_set = true;
145 assert(c.pthread_cond_broadcast(&self.cond) == 0);
146 }
147 }
148
149 fn wait(self: *PosixEvent, timeout: ?u64) !void {
150 assert(c.pthread_mutex_lock(&self.mutex) == 0);
151 defer assert(c.pthread_mutex_unlock(&self.mutex) == 0);
152
153 // quick guard before possibly calling time syscalls below
154 if (self.is_set)
155 return;
156
157 var ts: os.timespec = undefined;
158 if (timeout) |timeout_ns| {
159 var timeout_abs = timeout_ns;
160 if (comptime std.Target.current.isDarwin()) {
161 var tv: os.darwin.timeval = undefined;
162 assert(os.darwin.gettimeofday(&tv, null) == 0);
163 timeout_abs += @intCast(u64, tv.tv_sec) * time.ns_per_s;
164 timeout_abs += @intCast(u64, tv.tv_usec) * time.ns_per_us;
165 } else {
166 os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable;
167 timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s;
168 timeout_abs += @intCast(u64, ts.tv_nsec);
169 }
170 ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s));
171 ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s));
172 }
173
174 while (!self.is_set) {
175 const rc = switch (timeout == null) {
176 true => c.pthread_cond_wait(&self.cond, &self.mutex),
177 else => c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts),
178 };
179 switch (rc) {
180 0 => {},
181 os.ETIMEDOUT => return error.TimedOut,
182 os.EINVAL => unreachable,
183 os.EPERM => unreachable,
184 else => unreachable,
185 }
186 }
187 }
188};
189
190const AtomicEvent = struct {
191 waiters: u32,
192
193 const WAKE = 1 << 0;
194 const WAIT = 1 << 1;
195
196 fn init() AtomicEvent {
197 return AtomicEvent{ .waiters = 0 };
198 }
199
200 fn deinit(self: *AtomicEvent) void {
201 self.* = undefined;
202 }
203
204 fn isSet(self: *const AtomicEvent) bool {
205 return @atomicLoad(u32, &self.waiters, .Acquire) == WAKE;
206 }
207
208 fn reset(self: *AtomicEvent) void {
209 @atomicStore(u32, &self.waiters, 0, .Monotonic);
210 }
211
212 fn set(self: *AtomicEvent) void {
213 const waiters = @atomicRmw(u32, &self.waiters, .Xchg, WAKE, .Release);
214 if (waiters >= WAIT) {
215 return Futex.wake(&self.waiters, waiters >> 1);
216 }
217 }
218
219 fn wait(self: *AtomicEvent, timeout: ?u64) !void {
220 var waiters = @atomicLoad(u32, &self.waiters, .Acquire);
221 while (waiters != WAKE) {
222 waiters = @cmpxchgWeak(u32, &self.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse return Futex.wait(&self.waiters, timeout);
223 }
224 }
225
226 pub const Futex = switch (builtin.os.tag) {
227 .windows => WindowsFutex,
228 .linux => LinuxFutex,
229 else => SpinFutex,
230 };
231
232 const SpinFutex = struct {
233 fn wake(waiters: *u32, wake_count: u32) void {}
234
235 fn wait(waiters: *u32, timeout: ?u64) !void {
236 // TODO: handle platforms where a monotonic timer isnt available
237 var timer: time.Timer = undefined;
238 if (timeout != null)
239 timer = time.Timer.start() catch unreachable;
240
241 while (@atomicLoad(u32, waiters, .Acquire) != WAKE) {
242 SpinLock.yield();
243 if (timeout) |timeout_ns| {
244 if (timer.read() >= timeout_ns)
245 return error.TimedOut;
246 }
247 }
248 }
249 };
250
251 const LinuxFutex = struct {
252 fn wake(waiters: *u32, wake_count: u32) void {
253 const waiting = std.math.maxInt(i32); // wake_count
254 const ptr = @ptrCast(*const i32, waiters);
255 const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting);
256 assert(linux.getErrno(rc) == 0);
257 }
258
259 fn wait(waiters: *u32, timeout: ?u64) !void {
260 var ts: linux.timespec = undefined;
261 var ts_ptr: ?*linux.timespec = null;
262 if (timeout) |timeout_ns| {
263 ts_ptr = &ts;
264 ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s);
265 ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s);
266 }
267
268 while (true) {
269 const waiting = @atomicLoad(u32, waiters, .Acquire);
270 if (waiting == WAKE)
271 return;
272 const expected = @intCast(i32, waiting);
273 const ptr = @ptrCast(*const i32, waiters);
274 const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr);
275 switch (linux.getErrno(rc)) {
276 0 => continue,
277 os.ETIMEDOUT => return error.TimedOut,
278 os.EINTR => continue,
279 os.EAGAIN => return,
280 else => unreachable,
281 }
282 }
283 }
284 };
285
286 const WindowsFutex = struct {
287 pub fn wake(waiters: *u32, wake_count: u32) void {
288 const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count);
289 const key = @ptrCast(*const c_void, waiters);
290
291 var waiting = wake_count;
292 while (waiting != 0) : (waiting -= 1) {
293 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
294 assert(rc == .SUCCESS);
295 }
296 }
297
298 pub fn wait(waiters: *u32, timeout: ?u64) !void {
299 const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout);
300 const key = @ptrCast(*const c_void, waiters);
301
302 // NT uses timeouts in units of 100ns with negative value being relative
303 var timeout_ptr: ?*windows.LARGE_INTEGER = null;
304 var timeout_value: windows.LARGE_INTEGER = undefined;
305 if (timeout) |timeout_ns| {
306 timeout_ptr = &timeout_value;
307 timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100);
308 }
309
310 // NtWaitForKeyedEvent doesnt have spurious wake-ups
311 var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr);
312 switch (rc) {
313 .TIMEOUT => {
314 // update the wait count to signal that we're not waiting anymore.
315 // if the .set() thread already observed that we are, perform a
316 // matching NtWaitForKeyedEvent so that the .set() thread doesn't
317 // deadlock trying to run NtReleaseKeyedEvent above.
318 var waiting = @atomicLoad(u32, waiters, .Monotonic);
319 while (true) {
320 if (waiting == WAKE) {
321 rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
322 assert(rc == .WAIT_0);
323 break;
324 } else {
325 waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break;
326 continue;
327 }
328 }
329 return error.TimedOut;
330 },
331 .WAIT_0 => {},
332 else => unreachable,
333 }
334 }
335
336 var event_handle: usize = EMPTY;
337 const EMPTY = ~@as(usize, 0);
338 const LOADING = EMPTY - 1;
339
340 pub fn getEventHandle() ?windows.HANDLE {
341 var handle = @atomicLoad(usize, &event_handle, .Monotonic);
342 while (true) {
343 switch (handle) {
344 EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse {
345 const handle_ptr = @ptrCast(*windows.HANDLE, &handle);
346 const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE;
347 if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS)
348 handle = 0;
349 @atomicStore(usize, &event_handle, handle, .Monotonic);
350 return @intToPtr(?windows.HANDLE, handle);
351 },
352 LOADING => {
353 SpinLock.yield();
354 handle = @atomicLoad(usize, &event_handle, .Monotonic);
355 },
356 else => {
357 return @intToPtr(?windows.HANDLE, handle);
358 },
359 }
360 }
361 }
362 };
363};
364
365test "ResetEvent" {
366 var event = ResetEvent.init();
367 defer event.deinit();
368
369 // test event setting
370 testing.expect(event.isSet() == false);
371 event.set();
372 testing.expect(event.isSet() == true);
373
374 // test event resetting
375 event.reset();
376 testing.expect(event.isSet() == false);
377
378 // test event waiting (non-blocking)
379 event.set();
380 event.wait();
381 try event.timedWait(1);
382
383 // test cross-thread signaling
384 if (builtin.single_threaded)
385 return;
386
387 const Context = struct {
388 const Self = @This();
389
390 value: u128,
391 in: ResetEvent,
392 out: ResetEvent,
393
394 fn init() Self {
395 return Self{
396 .value = 0,
397 .in = ResetEvent.init(),
398 .out = ResetEvent.init(),
399 };
400 }
401
402 fn deinit(self: *Self) void {
403 self.in.deinit();
404 self.out.deinit();
405 self.* = undefined;
406 }
407
408 fn sender(self: *Self) void {
409 // update value and signal input
410 testing.expect(self.value == 0);
411 self.value = 1;
412 self.in.set();
413
414 // wait for receiver to update value and signal output
415 self.out.wait();
416 testing.expect(self.value == 2);
417
418 // update value and signal final input
419 self.value = 3;
420 self.in.set();
421 }
422
423 fn receiver(self: *Self) void {
424 // wait for sender to update value and signal input
425 self.in.wait();
426 assert(self.value == 1);
427
428 // update value and signal output
429 self.in.reset();
430 self.value = 2;
431 self.out.set();
432
433 // wait for sender to update value and signal final input
434 self.in.wait();
435 assert(self.value == 3);
436 }
437
438 fn sleeper(self: *Self) void {
439 self.in.set();
440 time.sleep(time.ns_per_ms * 2);
441 self.value = 5;
442 self.out.set();
443 }
444
445 fn timedWaiter(self: *Self) !void {
446 self.in.wait();
447 testing.expectError(error.TimedOut, self.out.timedWait(time.ns_per_us));
448 try self.out.timedWait(time.ns_per_ms * 100);
449 testing.expect(self.value == 5);
450 }
451 };
452
453 var context = Context.init();
454 defer context.deinit();
455 const receiver = try std.Thread.spawn(&context, Context.receiver);
456 defer receiver.wait();
457 context.sender();
458
459 if (false) {
460 // I have now observed this fail on macOS, Windows, and Linux.
461 // https://github.com/ziglang/zig/issues/7009
462 var timed = Context.init();
463 defer timed.deinit();
464 const sleeper = try std.Thread.spawn(&timed, Context.sleeper);
465 defer sleeper.wait();
466 try timed.timedWaiter();
467 }
468}
lib/std/std.zig+2-1
......@@ -30,10 +30,11 @@ pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
3030pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
3131pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
3232pub const Progress = @import("Progress.zig");
33pub const ResetEvent = @import("reset_event.zig").ResetEvent;
33pub const ResetEvent = @import("ResetEvent.zig");
3434pub const SemanticVersion = @import("SemanticVersion.zig");
3535pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
3636pub const SpinLock = @import("spinlock.zig").SpinLock;
37pub const StaticResetEvent = @import("StaticResetEvent.zig");
3738pub const StringHashMap = hash_map.StringHashMap;
3839pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;
3940pub const StringArrayHashMap = array_hash_map.StringArrayHashMap;
src/Compilation.zig+13-5
......@@ -135,6 +135,8 @@ emit_docs: ?EmitLoc,
135135
136136c_header: ?c_link.Header,
137137
138work_queue_wait_group: WaitGroup,
139
138140pub const InnerError = Module.InnerError;
139141
140142pub const CRTFile = struct {
......@@ -1006,11 +1008,15 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
10061008 .test_filter = options.test_filter,
10071009 .test_name_prefix = options.test_name_prefix,
10081010 .test_evented_io = options.test_evented_io,
1011 .work_queue_wait_group = undefined,
10091012 };
10101013 break :comp comp;
10111014 };
10121015 errdefer comp.destroy();
10131016
1017 try comp.work_queue_wait_group.init();
1018 errdefer comp.work_queue_wait_group.deinit();
1019
10141020 if (comp.bin_file.options.module) |mod| {
10151021 try comp.work_queue.writeItem(.{ .generate_builtin_zig = {} });
10161022 }
......@@ -1191,6 +1197,8 @@ pub fn destroy(self: *Compilation) void {
11911197 self.cache_parent.manifest_dir.close();
11921198 if (self.owned_link_dir) |*dir| dir.close();
11931199
1200 self.work_queue_wait_group.deinit();
1201
11941202 // This destroys `self`.
11951203 self.arena_state.promote(gpa).deinit();
11961204}
......@@ -1405,13 +1413,13 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
14051413 var arena = std.heap.ArenaAllocator.init(self.gpa);
14061414 defer arena.deinit();
14071415
1408 var wg = WaitGroup{};
1409 defer wg.wait();
1416 self.work_queue_wait_group.reset();
1417 defer self.work_queue_wait_group.wait();
14101418
14111419 while (self.c_object_work_queue.readItem()) |c_object| {
1412 wg.start();
1420 self.work_queue_wait_group.start();
14131421 try self.thread_pool.spawn(workerUpdateCObject, .{
1414 self, c_object, &c_comp_progress_node, &wg,
1422 self, c_object, &c_comp_progress_node, &self.work_queue_wait_group,
14151423 });
14161424 }
14171425
......@@ -1764,7 +1772,7 @@ fn workerUpdateCObject(
17641772 progress_node: *std.Progress.Node,
17651773 wg: *WaitGroup,
17661774) void {
1767 defer wg.stop();
1775 defer wg.finish();
17681776
17691777 comp.updateCObject(c_object, progress_node) catch |err| switch (err) {
17701778 error.AnalysisFail => return,
src/Event.zig deleted-43
......@@ -1,43 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std");
7const Event = @This();
8
9lock: std.Mutex = .{},
10event: std.ResetEvent = undefined,
11state: enum { empty, waiting, notified } = .empty,
12
13pub fn wait(self: *Event) void {
14 const held = self.lock.acquire();
15
16 switch (self.state) {
17 .empty => {
18 self.state = .waiting;
19 self.event = @TypeOf(self.event).init();
20 held.release();
21 self.event.wait();
22 self.event.deinit();
23 },
24 .waiting => unreachable,
25 .notified => held.release(),
26 }
27}
28
29pub fn set(self: *Event) void {
30 const held = self.lock.acquire();
31
32 switch (self.state) {
33 .empty => {
34 self.state = .notified;
35 held.release();
36 },
37 .waiting => {
38 held.release();
39 self.event.set();
40 },
41 .notified => unreachable,
42 }
43}
src/ThreadPool.zig+74-62
......@@ -9,70 +9,102 @@ const ThreadPool = @This();
99lock: std.Mutex = .{},
1010is_running: bool = true,
1111allocator: *std.mem.Allocator,
12running: usize = 0,
13threads: []*std.Thread,
12workers: []Worker,
1413run_queue: RunQueue = .{},
1514idle_queue: IdleQueue = .{},
1615
17const IdleQueue = std.SinglyLinkedList(std.AutoResetEvent);
16const IdleQueue = std.SinglyLinkedList(std.ResetEvent);
1817const RunQueue = std.SinglyLinkedList(Runnable);
1918const Runnable = struct {
2019 runFn: fn (*Runnable) void,
2120};
2221
22const Worker = struct {
23 pool: *ThreadPool,
24 thread: *std.Thread,
25 /// The node is for this worker only and must have an already initialized event
26 /// when the thread is spawned.
27 idle_node: IdleQueue.Node,
28
29 fn run(worker: *Worker) void {
30 while (true) {
31 const held = worker.pool.lock.acquire();
32
33 if (worker.pool.run_queue.popFirst()) |run_node| {
34 held.release();
35 (run_node.data.runFn)(&run_node.data);
36 continue;
37 }
38
39 if (worker.pool.is_running) {
40 worker.idle_node.data.reset();
41
42 worker.pool.idle_queue.prepend(&worker.idle_node);
43 held.release();
44
45 worker.idle_node.data.wait();
46 continue;
47 }
48
49 held.release();
50 return;
51 }
52 }
53};
54
2355pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {
2456 self.* = .{
2557 .allocator = allocator,
26 .threads = &[_]*std.Thread{},
58 .workers = &[_]Worker{},
2759 };
2860 if (std.builtin.single_threaded)
2961 return;
3062
31 errdefer self.deinit();
63 const worker_count = std.math.max(1, std.Thread.cpuCount() catch 1);
64 self.workers = try allocator.alloc(Worker, worker_count);
65 errdefer allocator.free(self.workers);
66
67 var worker_index: usize = 0;
68 errdefer self.destroyWorkers(worker_index);
69 while (worker_index < worker_count) : (worker_index += 1) {
70 const worker = &self.workers[worker_index];
71 worker.pool = self;
3272
33 var num_threads = std.Thread.cpuCount() catch 1;
34 if (num_threads > 0)
35 self.threads = try allocator.alloc(*std.Thread, num_threads);
73 // Each worker requires its ResetEvent to be pre-initialized.
74 try worker.idle_node.data.init();
75 errdefer worker.idle_node.data.deinit();
3676
37 while (num_threads > 0) : (num_threads -= 1) {
38 const thread = try std.Thread.spawn(self, runWorker);
39 self.threads[self.running] = thread;
40 self.running += 1;
77 worker.thread = try std.Thread.spawn(worker, Worker.run);
4178 }
4279}
4380
44pub fn deinit(self: *ThreadPool) void {
45 self.shutdown();
46
47 std.debug.assert(!self.is_running);
48 for (self.threads[0..self.running]) |thread|
49 thread.wait();
50
51 defer self.threads = &[_]*std.Thread{};
52 if (self.running > 0)
53 self.allocator.free(self.threads);
81fn destroyWorkers(self: *ThreadPool, spawned: usize) void {
82 for (self.workers[0..spawned]) |*worker| {
83 worker.thread.wait();
84 worker.idle_node.data.deinit();
85 }
5486}
5587
56pub fn shutdown(self: *ThreadPool) void {
57 const held = self.lock.acquire();
58
59 if (!self.is_running)
60 return held.release();
88pub fn deinit(self: *ThreadPool) void {
89 {
90 const held = self.lock.acquire();
91 defer held.release();
6192
62 var idle_queue = self.idle_queue;
63 self.idle_queue = .{};
64 self.is_running = false;
65 held.release();
93 self.is_running = false;
94 while (self.idle_queue.popFirst()) |idle_node|
95 idle_node.data.set();
96 }
6697
67 while (idle_queue.popFirst()) |idle_node|
68 idle_node.data.set();
98 self.destroyWorkers(self.workers.len);
99 self.allocator.free(self.workers);
69100}
70101
71102pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void {
72103 if (std.builtin.single_threaded) {
73 @call(.{}, func, args);
104 const result = @call(.{}, func, args);
74105 return;
75106 }
107
76108 const Args = @TypeOf(args);
77109 const Closure = struct {
78110 arguments: Args,
......@@ -83,44 +115,24 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void {
83115 const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable);
84116 const closure = @fieldParentPtr(@This(), "run_node", run_node);
85117 const result = @call(.{}, func, closure.arguments);
118
119 const held = closure.pool.lock.acquire();
120 defer held.release();
86121 closure.pool.allocator.destroy(closure);
87122 }
88123 };
89124
125 const held = self.lock.acquire();
126 defer held.release();
127
90128 const closure = try self.allocator.create(Closure);
91129 closure.* = .{
92130 .arguments = args,
93131 .pool = self,
94132 };
95133
96 const held = self.lock.acquire();
97134 self.run_queue.prepend(&closure.run_node);
98135
99 const idle_node = self.idle_queue.popFirst();
100 held.release();
101
102 if (idle_node) |node|
103 node.data.set();
104}
105
106fn runWorker(self: *ThreadPool) void {
107 while (true) {
108 const held = self.lock.acquire();
109
110 if (self.run_queue.popFirst()) |run_node| {
111 held.release();
112 (run_node.data.runFn)(&run_node.data);
113 continue;
114 }
115
116 if (!self.is_running) {
117 held.release();
118 return;
119 }
120
121 var idle_node = IdleQueue.Node{ .data = .{} };
122 self.idle_queue.prepend(&idle_node);
123 held.release();
124 idle_node.data.wait();
125 }
136 if (self.idle_queue.popFirst()) |idle_node|
137 idle_node.data.set();
126138}
src/WaitGroup.zig+33-18
......@@ -5,11 +5,24 @@
55// and substantial portions of the software.
66const std = @import("std");
77const WaitGroup = @This();
8const Event = @import("Event.zig");
98
109lock: std.Mutex = .{},
1110counter: usize = 0,
12event: ?*Event = null,
11event: std.ResetEvent,
12
13pub fn init(self: *WaitGroup) !void {
14 self.* = .{
15 .lock = .{},
16 .counter = 0,
17 .event = undefined,
18 };
19 try self.event.init();
20}
21
22pub fn deinit(self: *WaitGroup) void {
23 self.event.deinit();
24 self.* = undefined;
25}
1326
1427pub fn start(self: *WaitGroup) void {
1528 const held = self.lock.acquire();
......@@ -18,29 +31,31 @@ pub fn start(self: *WaitGroup) void {
1831 self.counter += 1;
1932}
2033
21pub fn stop(self: *WaitGroup) void {
22 var event: ?*Event = null;
23 defer if (event) |waiter|
24 waiter.set();
25
34pub fn finish(self: *WaitGroup) void {
2635 const held = self.lock.acquire();
2736 defer held.release();
2837
2938 self.counter -= 1;
30 if (self.counter == 0)
31 std.mem.swap(?*Event, &self.event, &event);
39
40 if (self.counter == 0) {
41 self.event.set();
42 }
3243}
3344
3445pub fn wait(self: *WaitGroup) void {
35 var event = Event{};
36 var has_event = false;
37 defer if (has_event)
38 event.wait();
46 while (true) {
47 const held = self.lock.acquire();
3948
40 const held = self.lock.acquire();
41 defer held.release();
49 if (self.counter == 0) {
50 held.release();
51 return;
52 }
53
54 held.release();
55 self.event.wait();
56 }
57}
4258
43 has_event = self.counter != 0;
44 if (has_event)
45 self.event = &event;
59pub fn reset(self: *WaitGroup) void {
60 self.event.reset();
4661}