authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-23 16:57:18-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-23 16:57:18-08:00
log177377b6e356b34bbed40cadca596658d158af6b
treeed7e0a7fa146b8c15044e21f386ec8a8e2977695
parent5377b7fb97311448daa3c29a8c8f100656d871ba

rework std.ResetEvent, improve std lib Darwin integration

* split std.ResetEvent into: - ResetEvent - requires init() at runtime and it can fail. Also requires deinit(). - StaticResetEvent - can be statically initialized and requires no deinitialization. Initialization cannot fail. * the POSIX sem_t implementation can in fact fail on initialization because it is allowed to be implemented as a file descriptor. * Completely define, clarify, and explain in detail the semantics of these APIs. Remove the `isSet` function. * `ResetEvent.timedWait` returns an enum instead of a possible error. * `ResetEvent.init` takes a pointer to the ResetEvent instead of returning a copy. * On Darwin, `ResetEvent` is implemented using Grand Central Dispatch, which is exposed by libSystem. stage2 changes: * ThreadPool: use a single, pre-initialized `ResetEvent` per worker. * WaitGroup: now requires init() and deinit() and init() can fail. - Add a `reset` function. - Compilation initializes one for the work queue in creation and re-uses it for every update. - Rename `stop` to `finish`. - Simplify the implementation based on the usage pattern.

12 files changed, 805 insertions(+), 567 deletions(-)

CMakeLists.txt+3-2
......@@ -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"
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 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/c/darwin.zig+12
......@@ -187,3 +187,15 @@ pub const pthread_attr_t = extern struct {
187187};
188188
189189pub 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/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
......@@ -771,8 +771,6 @@ test "open file with exclusive lock twice, make sure it waits" {
771771 std.time.sleep(SLEEP_TIMEOUT_NS);
772772 if (timer.read() >= SLEEP_TIMEOUT_NS) break;
773773 }
774 // Check that createFile is still waiting for the lock to be released.
775 testing.expect(!evt.isSet());
776774 file.close();
777775 // No timeout to avoid failures on heavily loaded systems.
778776 evt.wait();
lib/std/mutex.zig+2-2
......@@ -284,7 +284,7 @@ const WindowsMutex = struct {
284284 fn acquireSlow(self: *WindowsMutex) Held {
285285 // try to use NT keyed events for blocking, falling back to spinlock if unavailable
286286 @setCold(true);
287 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning();
287 const handle = ResetEvent.Impl.Futex.getEventHandle() orelse return self.acquireSpinning();
288288 const key = @ptrCast(*const c_void, &self.state.waiters);
289289
290290 while (true) : (SpinLock.loopHint(1)) {
......@@ -312,7 +312,7 @@ const WindowsMutex = struct {
312312 pub fn release(self: Held) void {
313313 // unlock without a rmw/cmpxchg instruction
314314 @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release);
315 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return;
315 const handle = ResetEvent.Impl.Futex.getEventHandle() orelse return;
316316 const key = @ptrCast(*const c_void, &self.mutex.state.waiters);
317317
318318 while (true) : (SpinLock.loopHint(1)) {
lib/std/reset_event.zig deleted-501
......@@ -1,501 +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 (std.Thread.use_pthreads)
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 /// When `wait` would return without blocking, this returns `true`.
38 /// Note that the value may be immediately invalid upon this function's
39 /// return, because another thread may call `wait` in between, changing
40 /// the event's set/cleared status.
41 pub fn isSet(self: *ResetEvent) bool {
42 return self.os_event.isSet();
43 }
44
45 /// Sets the event if not already set and
46 /// wakes up all the threads waiting on the event.
47 pub fn set(self: *ResetEvent) void {
48 return self.os_event.set();
49 }
50
51 /// Resets the event to its original, unset state.
52 /// TODO improve these docs:
53 /// * under what circumstances does it make sense to call this function?
54 pub fn reset(self: *ResetEvent) void {
55 return self.os_event.reset();
56 }
57
58 /// Wait for the event to be set by blocking the current thread.
59 /// TODO improve these docs:
60 /// * is the function thread-safe?
61 /// * does it have suprious wakeups?
62 pub fn wait(self: *ResetEvent) void {
63 return self.os_event.wait();
64 }
65
66 /// Wait for the event to be set by blocking the current thread.
67 /// A timeout in nanoseconds can be provided as a hint for how
68 /// long the thread should block on the unset event before throwing error.TimedOut.
69 /// TODO improve these docs:
70 /// * is the function thread-safe?
71 /// * does it have suprious wakeups?
72 pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void {
73 return self.os_event.timedWait(timeout_ns);
74 }
75};
76
77const DebugEvent = struct {
78 is_set: bool,
79
80 fn init() DebugEvent {
81 return DebugEvent{ .is_set = false };
82 }
83
84 fn deinit(self: *DebugEvent) void {
85 self.* = undefined;
86 }
87
88 fn isSet(self: *DebugEvent) bool {
89 return self.is_set;
90 }
91
92 fn reset(self: *DebugEvent) void {
93 self.is_set = false;
94 }
95
96 fn set(self: *DebugEvent) void {
97 self.is_set = true;
98 }
99
100 fn wait(self: *DebugEvent) void {
101 if (self.is_set)
102 return;
103
104 @panic("deadlock detected");
105 }
106
107 fn timedWait(self: *DebugEvent, timeout: u64) !void {
108 if (self.is_set)
109 return;
110
111 return error.TimedOut;
112 }
113};
114
115const PosixEvent = struct {
116 sem: c.sem_t = undefined,
117 /// Sadly this is needed because pthreads semaphore API does not
118 /// support static initialization.
119 init_mutex: std.mutex.PthreadMutex = .{},
120 state: enum { uninit, init } = .uninit,
121
122 fn init() PosixEvent {
123 return .{};
124 }
125
126 /// Not thread-safe.
127 fn deinit(self: *PosixEvent) void {
128 switch (self.state) {
129 .uninit => {},
130 .init => {
131 assert(c.sem_destroy(&self.sem) == 0);
132 },
133 }
134 self.* = undefined;
135 }
136
137 fn isSet(self: *PosixEvent) bool {
138 const sem = self.getInitializedSem();
139 var val: c_int = undefined;
140 assert(c.sem_getvalue(sem, &val) == 0);
141 return val > 0;
142 }
143
144 fn reset(self: *PosixEvent) void {
145 const sem = self.getInitializedSem();
146 while (true) {
147 switch (c.getErrno(c.sem_trywait(sem))) {
148 0 => continue, // Need to make it go to zero.
149 c.EINTR => continue,
150 c.EINVAL => unreachable,
151 c.EAGAIN => return, // The semaphore currently has the value zero.
152 else => unreachable,
153 }
154 }
155 }
156
157 fn set(self: *PosixEvent) void {
158 const sem = self.getInitializedSem();
159 assert(c.sem_post(sem) == 0);
160 }
161
162 fn wait(self: *PosixEvent) void {
163 const sem = self.getInitializedSem();
164 while (true) {
165 switch (c.getErrno(c.sem_wait(sem))) {
166 0 => return,
167 c.EINTR => continue,
168 c.EINVAL => unreachable,
169 else => unreachable,
170 }
171 }
172 }
173
174 fn timedWait(self: *PosixEvent, timeout_ns: u64) !void {
175 var ts: os.timespec = undefined;
176 var timeout_abs = timeout_ns;
177 if (comptime std.Target.current.isDarwin()) {
178 var tv: os.darwin.timeval = undefined;
179 assert(os.darwin.gettimeofday(&tv, null) == 0);
180 timeout_abs += @intCast(u64, tv.tv_sec) * time.ns_per_s;
181 timeout_abs += @intCast(u64, tv.tv_usec) * time.ns_per_us;
182 } else {
183 os.clock_gettime(os.CLOCK_REALTIME, &ts) catch return error.TimedOut;
184 timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s;
185 timeout_abs += @intCast(u64, ts.tv_nsec);
186 }
187 ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s));
188 ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s));
189 const sem = self.getInitializedSem();
190 while (true) {
191 switch (c.getErrno(c.sem_timedwait(&self.sem, &ts))) {
192 0 => return,
193 c.EINTR => continue,
194 c.EINVAL => unreachable,
195 c.ETIMEDOUT => return error.TimedOut,
196 else => unreachable,
197 }
198 }
199 }
200
201 fn getInitializedSem(self: *PosixEvent) *c.sem_t {
202 const held = self.init_mutex.acquire();
203 defer held.release();
204
205 switch (self.state) {
206 .init => return &self.sem,
207 .uninit => {
208 self.state = .init;
209 assert(c.sem_init(&self.sem, 0, 0) == 0);
210 return &self.sem;
211 },
212 }
213 }
214};
215
216const AtomicEvent = struct {
217 waiters: u32,
218
219 const WAKE = 1 << 0;
220 const WAIT = 1 << 1;
221
222 fn init() AtomicEvent {
223 return AtomicEvent{ .waiters = 0 };
224 }
225
226 fn deinit(self: *AtomicEvent) void {
227 self.* = undefined;
228 }
229
230 fn isSet(self: *const AtomicEvent) bool {
231 return @atomicLoad(u32, &self.waiters, .Acquire) == WAKE;
232 }
233
234 fn reset(self: *AtomicEvent) void {
235 @atomicStore(u32, &self.waiters, 0, .Monotonic);
236 }
237
238 fn set(self: *AtomicEvent) void {
239 const waiters = @atomicRmw(u32, &self.waiters, .Xchg, WAKE, .Release);
240 if (waiters >= WAIT) {
241 return Futex.wake(&self.waiters, waiters >> 1);
242 }
243 }
244
245 fn wait(self: *AtomicEvent) void {
246 return self.timedWait(null) catch unreachable;
247 }
248
249 fn timedWait(self: *AtomicEvent, timeout: ?u64) !void {
250 var waiters = @atomicLoad(u32, &self.waiters, .Acquire);
251 while (waiters != WAKE) {
252 waiters = @cmpxchgWeak(u32, &self.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse return Futex.wait(&self.waiters, timeout);
253 }
254 }
255
256 pub const Futex = switch (builtin.os.tag) {
257 .windows => WindowsFutex,
258 .linux => LinuxFutex,
259 else => SpinFutex,
260 };
261
262 const SpinFutex = struct {
263 fn wake(waiters: *u32, wake_count: u32) void {}
264
265 fn wait(waiters: *u32, timeout: ?u64) !void {
266 // TODO: handle platforms where a monotonic timer isnt available
267 var timer: time.Timer = undefined;
268 if (timeout != null)
269 timer = time.Timer.start() catch unreachable;
270
271 while (@atomicLoad(u32, waiters, .Acquire) != WAKE) {
272 SpinLock.yield();
273 if (timeout) |timeout_ns| {
274 if (timer.read() >= timeout_ns)
275 return error.TimedOut;
276 }
277 }
278 }
279 };
280
281 const LinuxFutex = struct {
282 fn wake(waiters: *u32, wake_count: u32) void {
283 const waiting = std.math.maxInt(i32); // wake_count
284 const ptr = @ptrCast(*const i32, waiters);
285 const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting);
286 assert(linux.getErrno(rc) == 0);
287 }
288
289 fn wait(waiters: *u32, timeout: ?u64) !void {
290 var ts: linux.timespec = undefined;
291 var ts_ptr: ?*linux.timespec = null;
292 if (timeout) |timeout_ns| {
293 ts_ptr = &ts;
294 ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s);
295 ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s);
296 }
297
298 while (true) {
299 const waiting = @atomicLoad(u32, waiters, .Acquire);
300 if (waiting == WAKE)
301 return;
302 const expected = @intCast(i32, waiting);
303 const ptr = @ptrCast(*const i32, waiters);
304 const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr);
305 switch (linux.getErrno(rc)) {
306 0 => continue,
307 os.ETIMEDOUT => return error.TimedOut,
308 os.EINTR => continue,
309 os.EAGAIN => return,
310 else => unreachable,
311 }
312 }
313 }
314 };
315
316 const WindowsFutex = struct {
317 pub fn wake(waiters: *u32, wake_count: u32) void {
318 const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count);
319 const key = @ptrCast(*const c_void, waiters);
320
321 var waiting = wake_count;
322 while (waiting != 0) : (waiting -= 1) {
323 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
324 assert(rc == .SUCCESS);
325 }
326 }
327
328 pub fn wait(waiters: *u32, timeout: ?u64) !void {
329 const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout);
330 const key = @ptrCast(*const c_void, waiters);
331
332 // NT uses timeouts in units of 100ns with negative value being relative
333 var timeout_ptr: ?*windows.LARGE_INTEGER = null;
334 var timeout_value: windows.LARGE_INTEGER = undefined;
335 if (timeout) |timeout_ns| {
336 timeout_ptr = &timeout_value;
337 timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100);
338 }
339
340 // NtWaitForKeyedEvent doesnt have spurious wake-ups
341 var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr);
342 switch (rc) {
343 .TIMEOUT => {
344 // update the wait count to signal that we're not waiting anymore.
345 // if the .set() thread already observed that we are, perform a
346 // matching NtWaitForKeyedEvent so that the .set() thread doesn't
347 // deadlock trying to run NtReleaseKeyedEvent above.
348 var waiting = @atomicLoad(u32, waiters, .Monotonic);
349 while (true) {
350 if (waiting == WAKE) {
351 rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
352 assert(rc == .WAIT_0);
353 break;
354 } else {
355 waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break;
356 continue;
357 }
358 }
359 return error.TimedOut;
360 },
361 .WAIT_0 => {},
362 else => unreachable,
363 }
364 }
365
366 var event_handle: usize = EMPTY;
367 const EMPTY = ~@as(usize, 0);
368 const LOADING = EMPTY - 1;
369
370 pub fn getEventHandle() ?windows.HANDLE {
371 var handle = @atomicLoad(usize, &event_handle, .Monotonic);
372 while (true) {
373 switch (handle) {
374 EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse {
375 const handle_ptr = @ptrCast(*windows.HANDLE, &handle);
376 const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE;
377 if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS)
378 handle = 0;
379 @atomicStore(usize, &event_handle, handle, .Monotonic);
380 return @intToPtr(?windows.HANDLE, handle);
381 },
382 LOADING => {
383 SpinLock.yield();
384 handle = @atomicLoad(usize, &event_handle, .Monotonic);
385 },
386 else => {
387 return @intToPtr(?windows.HANDLE, handle);
388 },
389 }
390 }
391 }
392 };
393};
394
395test "ResetEvent" {
396 var event = ResetEvent.init();
397 defer event.deinit();
398
399 // test event setting
400 testing.expect(!event.isSet());
401 event.set();
402 testing.expect(event.isSet());
403
404 // test event resetting
405 event.reset();
406 testing.expect(!event.isSet());
407
408 // test event waiting (non-blocking)
409 event.set();
410 event.wait();
411 event.reset();
412
413 event.set();
414 try event.timedWait(1);
415
416 // test cross-thread signaling
417 if (builtin.single_threaded)
418 return;
419
420 const Context = struct {
421 const Self = @This();
422
423 value: u128,
424 in: ResetEvent,
425 out: ResetEvent,
426
427 fn init() Self {
428 return Self{
429 .value = 0,
430 .in = ResetEvent.init(),
431 .out = ResetEvent.init(),
432 };
433 }
434
435 fn deinit(self: *Self) void {
436 self.in.deinit();
437 self.out.deinit();
438 self.* = undefined;
439 }
440
441 fn sender(self: *Self) void {
442 // update value and signal input
443 testing.expect(self.value == 0);
444 self.value = 1;
445 self.in.set();
446
447 // wait for receiver to update value and signal output
448 self.out.wait();
449 testing.expect(self.value == 2);
450
451 // update value and signal final input
452 self.value = 3;
453 self.in.set();
454 }
455
456 fn receiver(self: *Self) void {
457 // wait for sender to update value and signal input
458 self.in.wait();
459 assert(self.value == 1);
460
461 // update value and signal output
462 self.in.reset();
463 self.value = 2;
464 self.out.set();
465
466 // wait for sender to update value and signal final input
467 self.in.wait();
468 assert(self.value == 3);
469 }
470
471 fn sleeper(self: *Self) void {
472 self.in.set();
473 time.sleep(time.ns_per_ms * 2);
474 self.value = 5;
475 self.out.set();
476 }
477
478 fn timedWaiter(self: *Self) !void {
479 self.in.wait();
480 testing.expectError(error.TimedOut, self.out.timedWait(time.ns_per_us));
481 try self.out.timedWait(time.ns_per_ms * 100);
482 testing.expect(self.value == 5);
483 }
484 };
485
486 var context = Context.init();
487 defer context.deinit();
488 const receiver = try std.Thread.spawn(&context, Context.receiver);
489 defer receiver.wait();
490 context.sender();
491
492 if (false) {
493 // I have now observed this fail on macOS, Windows, and Linux.
494 // https://github.com/ziglang/zig/issues/7009
495 var timed = Context.init();
496 defer timed.deinit();
497 const sleeper = try std.Thread.spawn(&timed, Context.sleeper);
498 defer sleeper.wait();
499 try timed.timedWaiter();
500 }
501}
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/ThreadPool.zig+57-39
......@@ -9,8 +9,7 @@ const ThreadPool = @This();
99lock: std.Mutex = .{},
1010is_running: bool = true,
1111allocator: *std.mem.Allocator,
12spawned: usize = 0,
13threads: []*std.Thread,
12workers: []Worker,
1413run_queue: RunQueue = .{},
1514idle_queue: IdleQueue = .{},
1615
......@@ -20,23 +19,69 @@ const 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.math.max(1, std.Thread.cpuCount() catch 1);
34 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();
76
77 worker.thread = try std.Thread.spawn(worker, Worker.run);
78 }
79}
3580
36 while (num_threads > 0) : (num_threads -= 1) {
37 const thread = try std.Thread.spawn(self, runWorker);
38 self.threads[self.spawned] = thread;
39 self.spawned += 1;
81fn destroyWorkers(self: *ThreadPool, spawned: usize) void {
82 for (self.workers[0..spawned]) |*worker| {
83 worker.thread.wait();
84 worker.idle_node.data.deinit();
4085 }
4186}
4287
......@@ -50,9 +95,8 @@ pub fn deinit(self: *ThreadPool) void {
5095 idle_node.data.set();
5196 }
5297
53 defer self.allocator.free(self.threads);
54 for (self.threads[0..self.spawned]) |thread|
55 thread.wait();
98 self.destroyWorkers(self.workers.len);
99 self.allocator.free(self.workers);
56100}
57101
58102pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void {
......@@ -92,29 +136,3 @@ pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void {
92136 if (self.idle_queue.popFirst()) |idle_node|
93137 idle_node.data.set();
94138}
95
96fn runWorker(self: *ThreadPool) void {
97 while (true) {
98 const held = self.lock.acquire();
99
100 if (self.run_queue.popFirst()) |run_node| {
101 held.release();
102 (run_node.data.runFn)(&run_node.data);
103 continue;
104 }
105
106 if (self.is_running) {
107 var idle_node = IdleQueue.Node{ .data = std.ResetEvent.init() };
108
109 self.idle_queue.prepend(&idle_node);
110 held.release();
111
112 idle_node.data.wait();
113 idle_node.data.deinit();
114 continue;
115 }
116
117 held.release();
118 return;
119 }
120}
src/WaitGroup.zig+22-13
......@@ -8,7 +8,21 @@ const WaitGroup = @This();
88
99lock: std.Mutex = .{},
1010counter: usize = 0,
11event: ?*std.ResetEvent = 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}
1226
1327pub fn start(self: *WaitGroup) void {
1428 const held = self.lock.acquire();
......@@ -17,17 +31,14 @@ pub fn start(self: *WaitGroup) void {
1731 self.counter += 1;
1832}
1933
20pub fn stop(self: *WaitGroup) void {
34pub fn finish(self: *WaitGroup) void {
2135 const held = self.lock.acquire();
2236 defer held.release();
2337
2438 self.counter -= 1;
2539
2640 if (self.counter == 0) {
27 if (self.event) |event| {
28 self.event = null;
29 event.set();
30 }
41 self.event.set();
3142 }
3243}
3344
......@@ -40,13 +51,11 @@ pub fn wait(self: *WaitGroup) void {
4051 return;
4152 }
4253
43 var event = std.ResetEvent.init();
44 defer event.deinit();
45
46 std.debug.assert(self.event == null);
47 self.event = &event;
48
4954 held.release();
50 event.wait();
55 self.event.wait();
5156 }
5257}
58
59pub fn reset(self: *WaitGroup) void {
60 self.event.reset();
61}