authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-15 12:31:17-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-01-15 12:31:17-08:00
log35686262f503372bb9f38a71b891fd19b30adecd
tree5e9d3af75fc0c3d89ee001e2c922946fabb7d6ac
parent2faf8c53d226cec1445156cafdec59af5e909fb2
parent19f893c6bbb3f5ab4e8d943b0ac2fefa94e5d666
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7785 from ziglang/std-thread-primitives

organize std lib concurrency primitives and add RwLock

40 files changed, 2458 insertions(+), 1957 deletions(-)

CMakeLists.txt+5-6
...@@ -334,7 +334,6 @@ set(ZIG_STAGE2_SOURCES...@@ -334,7 +334,6 @@ set(ZIG_STAGE2_SOURCES
334 "${CMAKE_SOURCE_DIR}/lib/std/atomic/int.zig"334 "${CMAKE_SOURCE_DIR}/lib/std/atomic/int.zig"
335 "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig"335 "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig"
336 "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig"336 "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig"
337 "${CMAKE_SOURCE_DIR}/lib/std/auto_reset_event.zig"
338 "${CMAKE_SOURCE_DIR}/lib/std/base64.zig"337 "${CMAKE_SOURCE_DIR}/lib/std/base64.zig"
339 "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig"338 "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig"
340 "${CMAKE_SOURCE_DIR}/lib/std/builtin.zig"339 "${CMAKE_SOURCE_DIR}/lib/std/builtin.zig"
...@@ -409,7 +408,6 @@ set(ZIG_STAGE2_SOURCES...@@ -409,7 +408,6 @@ set(ZIG_STAGE2_SOURCES
409 "${CMAKE_SOURCE_DIR}/lib/std/meta.zig"408 "${CMAKE_SOURCE_DIR}/lib/std/meta.zig"
410 "${CMAKE_SOURCE_DIR}/lib/std/meta/trailer_flags.zig"409 "${CMAKE_SOURCE_DIR}/lib/std/meta/trailer_flags.zig"
411 "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"410 "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"
412 "${CMAKE_SOURCE_DIR}/lib/std/mutex.zig"
413 "${CMAKE_SOURCE_DIR}/lib/std/os.zig"411 "${CMAKE_SOURCE_DIR}/lib/std/os.zig"
414 "${CMAKE_SOURCE_DIR}/lib/std/os/bits.zig"412 "${CMAKE_SOURCE_DIR}/lib/std/os/bits.zig"
415 "${CMAKE_SOURCE_DIR}/lib/std/os/bits/linux.zig"413 "${CMAKE_SOURCE_DIR}/lib/std/os/bits/linux.zig"
...@@ -426,8 +424,6 @@ set(ZIG_STAGE2_SOURCES...@@ -426,8 +424,6 @@ set(ZIG_STAGE2_SOURCES
426 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"424 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"
427 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"425 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
428 "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"426 "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
429 "${CMAKE_SOURCE_DIR}/lib/std/ResetEvent.zig"
430 "${CMAKE_SOURCE_DIR}/lib/std/StaticResetEvent.zig"
431 "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"427 "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"
432 "${CMAKE_SOURCE_DIR}/lib/std/process.zig"428 "${CMAKE_SOURCE_DIR}/lib/std/process.zig"
433 "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"429 "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"
...@@ -494,7 +490,6 @@ set(ZIG_STAGE2_SOURCES...@@ -494,7 +490,6 @@ set(ZIG_STAGE2_SOURCES
494 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivmodti4.zig"490 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivmodti4.zig"
495 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivti3.zig"491 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/udivti3.zig"
496 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/umodti3.zig"492 "${CMAKE_SOURCE_DIR}/lib/std/special/compiler_rt/umodti3.zig"
497 "${CMAKE_SOURCE_DIR}/lib/std/SpinLock.zig"
498 "${CMAKE_SOURCE_DIR}/lib/std/start.zig"493 "${CMAKE_SOURCE_DIR}/lib/std/start.zig"
499 "${CMAKE_SOURCE_DIR}/lib/std/std.zig"494 "${CMAKE_SOURCE_DIR}/lib/std/std.zig"
500 "${CMAKE_SOURCE_DIR}/lib/std/target.zig"495 "${CMAKE_SOURCE_DIR}/lib/std/target.zig"
...@@ -513,7 +508,11 @@ set(ZIG_STAGE2_SOURCES...@@ -513,7 +508,11 @@ set(ZIG_STAGE2_SOURCES
513 "${CMAKE_SOURCE_DIR}/lib/std/target/systemz.zig"508 "${CMAKE_SOURCE_DIR}/lib/std/target/systemz.zig"
514 "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig"509 "${CMAKE_SOURCE_DIR}/lib/std/target/wasm.zig"
515 "${CMAKE_SOURCE_DIR}/lib/std/target/x86.zig"510 "${CMAKE_SOURCE_DIR}/lib/std/target/x86.zig"
516 "${CMAKE_SOURCE_DIR}/lib/std/thread.zig"511 "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig"
512 "${CMAKE_SOURCE_DIR}/lib/std/Thread/AutoResetEvent.zig"
513 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig"
514 "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig"
515 "${CMAKE_SOURCE_DIR}/lib/std/Thread/StaticResetEvent.zig"
517 "${CMAKE_SOURCE_DIR}/lib/std/time.zig"516 "${CMAKE_SOURCE_DIR}/lib/std/time.zig"
518 "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig"517 "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig"
519 "${CMAKE_SOURCE_DIR}/lib/std/zig.zig"518 "${CMAKE_SOURCE_DIR}/lib/std/zig.zig"
lib/std/Progress.zig+1-1
...@@ -50,7 +50,7 @@ done: bool = true,...@@ -50,7 +50,7 @@ done: bool = true,
50/// Protects the `refresh` function, as well as `node.recently_updated_child`.50/// Protects the `refresh` function, as well as `node.recently_updated_child`.
51/// Without this, callsites would call `Node.end` and then free `Node` memory51/// Without this, callsites would call `Node.end` and then free `Node` memory
52/// while it was still being accessed by the `refresh` function.52/// while it was still being accessed by the `refresh` function.
53update_lock: std.Mutex = .{},53update_lock: std.Thread.Mutex = .{},
5454
55/// Keeps track of how many columns in the terminal have been output, so that55/// Keeps track of how many columns in the terminal have been output, so that
56/// we can move the cursor back later.56/// we can move the cursor back later.
lib/std/ResetEvent.zig deleted-297
...@@ -1,297 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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/SpinLock.zig deleted-86
...@@ -1,86 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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//! A mutually exclusive lock that grinds the CPU rather than interacting with
7//! the operating system. It does however yield to the OS scheduler while
8//! spinning, when targeting an OS that supports it.
9//! This struct can be initialized directly and statically initialized. The
10//! default state is unlocked.
11
12state: State = State.Unlocked,
13
14const std = @import("std.zig");
15const builtin = @import("builtin");
16const SpinLock = @This();
17
18const State = enum(u8) {
19 Unlocked,
20 Locked,
21};
22
23pub const Held = struct {
24 spinlock: *SpinLock,
25
26 pub fn release(self: Held) void {
27 @atomicStore(State, &self.spinlock.state, .Unlocked, .Release);
28 }
29};
30
31pub fn tryAcquire(self: *SpinLock) ?Held {
32 return switch (@atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire)) {
33 .Unlocked => Held{ .spinlock = self },
34 .Locked => null,
35 };
36}
37
38pub fn acquire(self: *SpinLock) Held {
39 while (true) {
40 return self.tryAcquire() orelse {
41 yield();
42 continue;
43 };
44 }
45}
46
47pub fn yield() void {
48 // On native windows, SwitchToThread is too expensive,
49 // and yielding for 380-410 iterations was found to be
50 // a nice sweet spot. Posix systems on the other hand,
51 // especially linux, perform better by yielding the thread.
52 switch (builtin.os.tag) {
53 .windows => loopHint(400),
54 else => std.os.sched_yield() catch loopHint(1),
55 }
56}
57
58/// Hint to the cpu that execution is spinning
59/// for the given amount of iterations.
60pub fn loopHint(iterations: usize) void {
61 var i = iterations;
62 while (i != 0) : (i -= 1) {
63 switch (builtin.arch) {
64 // these instructions use a memory clobber as they
65 // flush the pipeline of any speculated reads/writes.
66 .i386, .x86_64 => asm volatile ("pause"
67 :
68 :
69 : "memory"
70 ),
71 .arm, .aarch64 => asm volatile ("yield"
72 :
73 :
74 : "memory"
75 ),
76 else => std.os.sched_yield() catch {},
77 }
78 }
79}
80
81test "basic usage" {
82 var lock: SpinLock = .{};
83
84 const held = lock.acquire();
85 defer held.release();
86}
lib/std/StaticResetEvent.zig deleted-396
...@@ -1,396 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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/Thread.zig created+560
...@@ -0,0 +1,560 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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//! This struct represents a kernel thread, and acts as a namespace for concurrency
8//! primitives that operate on kernel threads. For concurrency primitives that support
9//! both evented I/O and async I/O, see the respective names in the top level std namespace.
10
11data: Data,
12
13pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig");
14pub const ResetEvent = @import("Thread/ResetEvent.zig");
15pub const StaticResetEvent = @import("Thread/StaticResetEvent.zig");
16pub const Mutex = @import("Thread/Mutex.zig");
17pub const Semaphore = @import("Thread/Semaphore.zig");
18pub const Condition = @import("Thread/Condition.zig");
19
20pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc;
21
22const Thread = @This();
23const std = @import("std.zig");
24const builtin = std.builtin;
25const os = std.os;
26const mem = std.mem;
27const windows = std.os.windows;
28const c = std.c;
29const assert = std.debug.assert;
30
31const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
32
33/// Represents a kernel thread handle.
34/// May be an integer or a pointer depending on the platform.
35/// On Linux and POSIX, this is the same as Id.
36pub const Handle = if (use_pthreads)
37 c.pthread_t
38else switch (std.Target.current.os.tag) {
39 .linux => i32,
40 .windows => windows.HANDLE,
41 else => void,
42};
43
44/// Represents a unique ID per thread.
45/// May be an integer or pointer depending on the platform.
46/// On Linux and POSIX, this is the same as Handle.
47pub const Id = switch (std.Target.current.os.tag) {
48 .windows => windows.DWORD,
49 else => Handle,
50};
51
52pub const Data = if (use_pthreads)
53 struct {
54 handle: Thread.Handle,
55 memory: []u8,
56 }
57else switch (std.Target.current.os.tag) {
58 .linux => struct {
59 handle: Thread.Handle,
60 memory: []align(mem.page_size) u8,
61 },
62 .windows => struct {
63 handle: Thread.Handle,
64 alloc_start: *c_void,
65 heap_handle: windows.HANDLE,
66 },
67 else => struct {},
68};
69
70/// Signals the processor that it is inside a busy-wait spin-loop ("spin lock").
71pub fn spinLoopHint() void {
72 switch (std.Target.current.cpu.arch) {
73 .i386, .x86_64 => asm volatile ("pause"
74 :
75 :
76 : "memory"
77 ),
78 .arm, .aarch64 => asm volatile ("yield"
79 :
80 :
81 : "memory"
82 ),
83 else => {},
84 }
85}
86
87/// Returns the ID of the calling thread.
88/// Makes a syscall every time the function is called.
89/// On Linux and POSIX, this Id is the same as a Handle.
90pub fn getCurrentId() Id {
91 if (use_pthreads) {
92 return c.pthread_self();
93 } else
94 return switch (std.Target.current.os.tag) {
95 .linux => os.linux.gettid(),
96 .windows => windows.kernel32.GetCurrentThreadId(),
97 else => @compileError("Unsupported OS"),
98 };
99}
100
101/// Returns the handle of this thread.
102/// On Linux and POSIX, this is the same as Id.
103/// On Linux, it is possible that the thread spawned with `spawn`
104/// finishes executing entirely before the clone syscall completes. In this
105/// case, this function will return 0 rather than the no-longer-existing thread's
106/// pid.
107pub fn handle(self: Thread) Handle {
108 return self.data.handle;
109}
110
111pub fn wait(self: *Thread) void {
112 if (use_pthreads) {
113 const err = c.pthread_join(self.data.handle, null);
114 switch (err) {
115 0 => {},
116 os.EINVAL => unreachable,
117 os.ESRCH => unreachable,
118 os.EDEADLK => unreachable,
119 else => unreachable,
120 }
121 std.heap.c_allocator.free(self.data.memory);
122 std.heap.c_allocator.destroy(self);
123 } else switch (std.Target.current.os.tag) {
124 .linux => {
125 while (true) {
126 const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
127 if (pid_value == 0) break;
128 const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null);
129 switch (os.linux.getErrno(rc)) {
130 0 => continue,
131 os.EINTR => continue,
132 os.EAGAIN => continue,
133 else => unreachable,
134 }
135 }
136 os.munmap(self.data.memory);
137 },
138 .windows => {
139 windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable;
140 windows.CloseHandle(self.data.handle);
141 windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start);
142 },
143 else => @compileError("Unsupported OS"),
144 }
145}
146
147pub const SpawnError = error{
148 /// A system-imposed limit on the number of threads was encountered.
149 /// There are a number of limits that may trigger this error:
150 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
151 /// which limits the number of processes and threads for a real
152 /// user ID, was reached;
153 /// * the kernel's system-wide limit on the number of processes and
154 /// threads, /proc/sys/kernel/threads-max, was reached (see
155 /// proc(5));
156 /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was
157 /// reached (see proc(5)); or
158 /// * the PID limit (pids.max) imposed by the cgroup "process num‐
159 /// ber" (PIDs) controller was reached.
160 ThreadQuotaExceeded,
161
162 /// The kernel cannot allocate sufficient memory to allocate a task structure
163 /// for the child, or to copy those parts of the caller's context that need to
164 /// be copied.
165 SystemResources,
166
167 /// Not enough userland memory to spawn the thread.
168 OutOfMemory,
169
170 /// `mlockall` is enabled, and the memory needed to spawn the thread
171 /// would exceed the limit.
172 LockedMemoryLimitExceeded,
173
174 Unexpected,
175};
176
177/// caller must call wait on the returned thread
178/// fn startFn(@TypeOf(context)) T
179/// where T is u8, noreturn, void, or !void
180/// caller must call wait on the returned thread
181pub fn spawn(context: anytype, comptime startFn: anytype) SpawnError!*Thread {
182 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
183 // TODO compile-time call graph analysis to determine stack upper bound
184 // https://github.com/ziglang/zig/issues/157
185 const default_stack_size = 16 * 1024 * 1024;
186
187 const Context = @TypeOf(context);
188 comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context);
189
190 if (std.Target.current.os.tag == .windows) {
191 const WinThread = struct {
192 const OuterContext = struct {
193 thread: Thread,
194 inner: Context,
195 };
196 fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD {
197 const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
198
199 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
200 .NoReturn => {
201 startFn(arg);
202 },
203 .Void => {
204 startFn(arg);
205 return 0;
206 },
207 .Int => |info| {
208 if (info.bits != 8) {
209 @compileError(bad_startfn_ret);
210 }
211 return startFn(arg);
212 },
213 .ErrorUnion => |info| {
214 if (info.payload != void) {
215 @compileError(bad_startfn_ret);
216 }
217 startFn(arg) catch |err| {
218 std.debug.warn("error: {s}\n", .{@errorName(err)});
219 if (@errorReturnTrace()) |trace| {
220 std.debug.dumpStackTrace(trace.*);
221 }
222 };
223 return 0;
224 },
225 else => @compileError(bad_startfn_ret),
226 }
227 }
228 };
229
230 const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
231 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
232 const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;
233 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0);
234 const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];
235 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
236 outer_context.* = WinThread.OuterContext{
237 .thread = Thread{
238 .data = Thread.Data{
239 .heap_handle = heap_handle,
240 .alloc_start = bytes_ptr,
241 .handle = undefined,
242 },
243 },
244 .inner = context,
245 };
246
247 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);
248 outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {
249 switch (windows.kernel32.GetLastError()) {
250 else => |err| return windows.unexpectedError(err),
251 }
252 };
253 return &outer_context.thread;
254 }
255
256 const MainFuncs = struct {
257 fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 {
258 const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*;
259
260 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
261 .NoReturn => {
262 startFn(arg);
263 },
264 .Void => {
265 startFn(arg);
266 return 0;
267 },
268 .Int => |info| {
269 if (info.bits != 8) {
270 @compileError(bad_startfn_ret);
271 }
272 return startFn(arg);
273 },
274 .ErrorUnion => |info| {
275 if (info.payload != void) {
276 @compileError(bad_startfn_ret);
277 }
278 startFn(arg) catch |err| {
279 std.debug.warn("error: {s}\n", .{@errorName(err)});
280 if (@errorReturnTrace()) |trace| {
281 std.debug.dumpStackTrace(trace.*);
282 }
283 };
284 return 0;
285 },
286 else => @compileError(bad_startfn_ret),
287 }
288 }
289 fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void {
290 const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*;
291
292 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
293 .NoReturn => {
294 startFn(arg);
295 },
296 .Void => {
297 startFn(arg);
298 return null;
299 },
300 .Int => |info| {
301 if (info.bits != 8) {
302 @compileError(bad_startfn_ret);
303 }
304 // pthreads don't support exit status, ignore value
305 _ = startFn(arg);
306 return null;
307 },
308 .ErrorUnion => |info| {
309 if (info.payload != void) {
310 @compileError(bad_startfn_ret);
311 }
312 startFn(arg) catch |err| {
313 std.debug.warn("error: {s}\n", .{@errorName(err)});
314 if (@errorReturnTrace()) |trace| {
315 std.debug.dumpStackTrace(trace.*);
316 }
317 };
318 return null;
319 },
320 else => @compileError(bad_startfn_ret),
321 }
322 }
323 };
324
325 if (Thread.use_pthreads) {
326 var attr: c.pthread_attr_t = undefined;
327 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
328 defer assert(c.pthread_attr_destroy(&attr) == 0);
329
330 const thread_obj = try std.heap.c_allocator.create(Thread);
331 errdefer std.heap.c_allocator.destroy(thread_obj);
332 if (@sizeOf(Context) > 0) {
333 thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced(
334 u8,
335 @alignOf(Context),
336 @sizeOf(Context),
337 .at_least,
338 );
339 errdefer std.heap.c_allocator.free(thread_obj.data.memory);
340 mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context));
341 } else {
342 thread_obj.data.memory = @as([*]u8, undefined)[0..0];
343 }
344
345 // Use the same set of parameters used by the libc-less impl.
346 assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0);
347 assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0);
348
349 const err = c.pthread_create(
350 &thread_obj.data.handle,
351 &attr,
352 MainFuncs.posixThreadMain,
353 thread_obj.data.memory.ptr,
354 );
355 switch (err) {
356 0 => return thread_obj,
357 os.EAGAIN => return error.SystemResources,
358 os.EPERM => unreachable,
359 os.EINVAL => unreachable,
360 else => return os.unexpectedErrno(@intCast(usize, err)),
361 }
362
363 return thread_obj;
364 }
365
366 var guard_end_offset: usize = undefined;
367 var stack_end_offset: usize = undefined;
368 var thread_start_offset: usize = undefined;
369 var context_start_offset: usize = undefined;
370 var tls_start_offset: usize = undefined;
371 const mmap_len = blk: {
372 var l: usize = mem.page_size;
373 // Allocate a guard page right after the end of the stack region
374 guard_end_offset = l;
375 // The stack itself, which grows downwards.
376 l = mem.alignForward(l + default_stack_size, mem.page_size);
377 stack_end_offset = l;
378 // Above the stack, so that it can be in the same mmap call, put the Thread object.
379 l = mem.alignForward(l, @alignOf(Thread));
380 thread_start_offset = l;
381 l += @sizeOf(Thread);
382 // Next, the Context object.
383 if (@sizeOf(Context) != 0) {
384 l = mem.alignForward(l, @alignOf(Context));
385 context_start_offset = l;
386 l += @sizeOf(Context);
387 }
388 // Finally, the Thread Local Storage, if any.
389 l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align);
390 tls_start_offset = l;
391 l += os.linux.tls.tls_image.alloc_size;
392 // Round the size to the page size.
393 break :blk mem.alignForward(l, mem.page_size);
394 };
395
396 const mmap_slice = mem: {
397 // Map the whole stack with no rw permissions to avoid
398 // committing the whole region right away
399 const mmap_slice = os.mmap(
400 null,
401 mmap_len,
402 os.PROT_NONE,
403 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
404 -1,
405 0,
406 ) catch |err| switch (err) {
407 error.MemoryMappingNotSupported => unreachable,
408 error.AccessDenied => unreachable,
409 error.PermissionDenied => unreachable,
410 else => |e| return e,
411 };
412 errdefer os.munmap(mmap_slice);
413
414 // Map everything but the guard page as rw
415 os.mprotect(
416 mmap_slice[guard_end_offset..],
417 os.PROT_READ | os.PROT_WRITE,
418 ) catch |err| switch (err) {
419 error.AccessDenied => unreachable,
420 else => |e| return e,
421 };
422
423 break :mem mmap_slice;
424 };
425
426 const mmap_addr = @ptrToInt(mmap_slice.ptr);
427
428 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
429 thread_ptr.data.memory = mmap_slice;
430
431 var arg: usize = undefined;
432 if (@sizeOf(Context) != 0) {
433 arg = mmap_addr + context_start_offset;
434 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
435 context_ptr.* = context;
436 }
437
438 if (std.Target.current.os.tag == .linux) {
439 const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |
440 os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM |
441 os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
442 os.CLONE_DETACHED | os.CLONE_SETTLS;
443 // This structure is only needed when targeting i386
444 var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined;
445
446 const tls_area = mmap_slice[tls_start_offset..];
447 const tp_value = os.linux.tls.prepareTLS(tls_area);
448
449 const newtls = blk: {
450 if (std.Target.current.cpu.arch == .i386) {
451 user_desc = os.linux.user_desc{
452 .entry_number = os.linux.tls.tls_image.gdt_entry_number,
453 .base_addr = tp_value,
454 .limit = 0xfffff,
455 .seg_32bit = 1,
456 .contents = 0, // Data
457 .read_exec_only = 0,
458 .limit_in_pages = 1,
459 .seg_not_present = 0,
460 .useable = 1,
461 };
462 break :blk @ptrToInt(&user_desc);
463 } else {
464 break :blk tp_value;
465 }
466 };
467
468 const rc = os.linux.clone(
469 MainFuncs.linuxThreadMain,
470 mmap_addr + stack_end_offset,
471 flags,
472 arg,
473 &thread_ptr.data.handle,
474 newtls,
475 &thread_ptr.data.handle,
476 );
477 switch (os.errno(rc)) {
478 0 => return thread_ptr,
479 os.EAGAIN => return error.ThreadQuotaExceeded,
480 os.EINVAL => unreachable,
481 os.ENOMEM => return error.SystemResources,
482 os.ENOSPC => unreachable,
483 os.EPERM => unreachable,
484 os.EUSERS => unreachable,
485 else => |err| return os.unexpectedErrno(err),
486 }
487 } else {
488 @compileError("Unsupported OS");
489 }
490}
491
492pub const CpuCountError = error{
493 PermissionDenied,
494 SystemResources,
495 Unexpected,
496};
497
498pub fn cpuCount() CpuCountError!usize {
499 if (std.Target.current.os.tag == .linux) {
500 const cpu_set = try os.sched_getaffinity(0);
501 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
502 }
503 if (std.Target.current.os.tag == .windows) {
504 return os.windows.peb().NumberOfProcessors;
505 }
506 if (std.Target.current.os.tag == .openbsd) {
507 var count: c_int = undefined;
508 var count_size: usize = @sizeOf(c_int);
509 const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE };
510 os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
511 error.NameTooLong, error.UnknownName => unreachable,
512 else => |e| return e,
513 };
514 return @intCast(usize, count);
515 }
516 var count: c_int = undefined;
517 var count_len: usize = @sizeOf(c_int);
518 const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
519 os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
520 error.NameTooLong, error.UnknownName => unreachable,
521 else => |e| return e,
522 };
523 return @intCast(usize, count);
524}
525
526pub fn getCurrentThreadId() u64 {
527 switch (std.Target.current.os.tag) {
528 .linux => {
529 // Use the syscall directly as musl doesn't provide a wrapper.
530 return @bitCast(u32, os.linux.gettid());
531 },
532 .windows => {
533 return os.windows.kernel32.GetCurrentThreadId();
534 },
535 .macos, .ios, .watchos, .tvos => {
536 var thread_id: u64 = undefined;
537 // Pass thread=null to get the current thread ID.
538 assert(c.pthread_threadid_np(null, &thread_id) == 0);
539 return thread_id;
540 },
541 .netbsd => {
542 return @bitCast(u32, c._lwp_self());
543 },
544 .freebsd => {
545 return @bitCast(u32, c.pthread_getthreadid_np());
546 },
547 .openbsd => {
548 return @bitCast(u32, c.getthrid());
549 },
550 else => {
551 @compileError("getCurrentThreadId not implemented for this platform");
552 },
553 }
554}
555
556test "" {
557 if (!builtin.single_threaded) {
558 std.testing.refAllDecls(@This());
559 }
560}
lib/std/Thread/AutoResetEvent.zig created+228
...@@ -0,0 +1,228 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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//! Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`.
8//! Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like).
9//!
10//! AutoResetEvent has 3 possible states:
11//! - UNSET: the AutoResetEvent is currently unset
12//! - SET: the AutoResetEvent was notified before a wait() was called
13//! - <StaticResetEvent pointer>: there is an active waiter waiting for a notification.
14//!
15//! When attempting to wait:
16//! if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
17//! if the event is already set, then it consumes the notification and resets the event.
18//!
19//! When attempting to notify:
20//! if the event is unset, then we set the event
21//! if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
22//!
23//! This ensures that the event is automatically reset after a wait() has been issued
24//! and avoids the race condition when using StaticResetEvent in the following scenario:
25//! thread 1 | thread 2
26//! StaticResetEvent.wait() |
27//! | StaticResetEvent.set()
28//! | StaticResetEvent.set()
29//! StaticResetEvent.reset() |
30//! StaticResetEvent.wait() | (missed the second .set() notification above)
31
32state: usize = UNSET,
33
34const std = @import("../std.zig");
35const builtin = @import("builtin");
36const testing = std.testing;
37const assert = std.debug.assert;
38const StaticResetEvent = std.Thread.StaticResetEvent;
39const AutoResetEvent = @This();
40
41const UNSET = 0;
42const SET = 1;
43
44/// the minimum alignment for the `*StaticResetEvent` created by wait*()
45const event_align = std.math.max(@alignOf(StaticResetEvent), 2);
46
47pub fn wait(self: *AutoResetEvent) void {
48 self.waitFor(null) catch unreachable;
49}
50
51pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void {
52 return self.waitFor(timeout);
53}
54
55fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void {
56 // lazily initialized StaticResetEvent
57 var reset_event: StaticResetEvent align(event_align) = undefined;
58 var has_reset_event = false;
59
60 var state = @atomicLoad(usize, &self.state, .SeqCst);
61 while (true) {
62 // consume a notification if there is any
63 if (state == SET) {
64 @atomicStore(usize, &self.state, UNSET, .SeqCst);
65 return;
66 }
67
68 // check if theres currently a pending ResetEvent pointer already registered
69 if (state != UNSET) {
70 unreachable; // multiple waiting threads on the same AutoResetEvent
71 }
72
73 // lazily initialize the ResetEvent if it hasn't been already
74 if (!has_reset_event) {
75 has_reset_event = true;
76 reset_event = .{};
77 }
78
79 // Since the AutoResetEvent currently isnt set,
80 // try to register our ResetEvent on it to wait
81 // for a set() call from another thread.
82 if (@cmpxchgWeak(
83 usize,
84 &self.state,
85 UNSET,
86 @ptrToInt(&reset_event),
87 .SeqCst,
88 .SeqCst,
89 )) |new_state| {
90 state = new_state;
91 continue;
92 }
93
94 // if no timeout was specified, then just wait forever
95 const timeout_ns = timeout orelse {
96 reset_event.wait();
97 return;
98 };
99
100 // wait with a timeout and return if signalled via set()
101 switch (reset_event.timedWait(timeout_ns)) {
102 .event_set => return,
103 .timed_out => {},
104 }
105
106 // If we timed out, we need to transition the AutoResetEvent back to UNSET.
107 // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent.
108 state = @cmpxchgStrong(
109 usize,
110 &self.state,
111 @ptrToInt(&reset_event),
112 UNSET,
113 .SeqCst,
114 .SeqCst,
115 ) orelse return error.TimedOut;
116
117 // We didn't manage to unregister ourselves from the state.
118 if (state == SET) {
119 unreachable; // AutoResetEvent notified without waking up the waiting thread
120 } else if (state != UNSET) {
121 unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out
122 }
123
124 // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up.
125 // We need to wait for it to wake up our ResetEvent before we can return and invalidate it.
126 // We don't return error.TimedOut here as it technically notified us while we were "timing out".
127 reset_event.wait();
128 return;
129 }
130}
131
132pub fn set(self: *AutoResetEvent) void {
133 var state = @atomicLoad(usize, &self.state, .SeqCst);
134 while (true) {
135 // If the AutoResetEvent is already set, there is nothing else left to do
136 if (state == SET) {
137 return;
138 }
139
140 // If the AutoResetEvent isn't set,
141 // then try to leave a notification for the wait() thread that we set() it.
142 if (state == UNSET) {
143 state = @cmpxchgWeak(
144 usize,
145 &self.state,
146 UNSET,
147 SET,
148 .SeqCst,
149 .SeqCst,
150 ) orelse return;
151 continue;
152 }
153
154 // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting.
155 // Try to acquire ownership of it so that we can wake it up.
156 // This also resets the AutoResetEvent so that there is no race condition as defined above.
157 if (@cmpxchgWeak(
158 usize,
159 &self.state,
160 state,
161 UNSET,
162 .SeqCst,
163 .SeqCst,
164 )) |new_state| {
165 state = new_state;
166 continue;
167 }
168
169 const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state);
170 reset_event.set();
171 return;
172 }
173}
174
175test "basic usage" {
176 // test local code paths
177 {
178 var event = AutoResetEvent{};
179 testing.expectError(error.TimedOut, event.timedWait(1));
180 event.set();
181 event.wait();
182 }
183
184 // test cross-thread signaling
185 if (builtin.single_threaded)
186 return;
187
188 const Context = struct {
189 value: u128 = 0,
190 in: AutoResetEvent = AutoResetEvent{},
191 out: AutoResetEvent = AutoResetEvent{},
192
193 const Self = @This();
194
195 fn sender(self: *Self) void {
196 testing.expect(self.value == 0);
197 self.value = 1;
198 self.out.set();
199
200 self.in.wait();
201 testing.expect(self.value == 2);
202 self.value = 3;
203 self.out.set();
204
205 self.in.wait();
206 testing.expect(self.value == 4);
207 }
208
209 fn receiver(self: *Self) void {
210 self.out.wait();
211 testing.expect(self.value == 1);
212 self.value = 2;
213 self.in.set();
214
215 self.out.wait();
216 testing.expect(self.value == 3);
217 self.value = 4;
218 self.in.set();
219 }
220 };
221
222 var context = Context{};
223 const send_thread = try std.Thread.spawn(&context, Context.sender);
224 const recv_thread = try std.Thread.spawn(&context, Context.receiver);
225
226 send_thread.wait();
227 recv_thread.wait();
228}
lib/std/Thread/Condition.zig created+182
...@@ -0,0 +1,182 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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 condition provides a way for a kernel thread to block until it is signaled
8//! to wake up. Spurious wakeups are possible.
9//! This API supports static initialization and does not require deinitialization.
10
11impl: Impl,
12
13const std = @import("../std.zig");
14const Condition = @This();
15const windows = std.os.windows;
16const linux = std.os.linux;
17const Mutex = std.Thread.Mutex;
18const assert = std.debug.assert;
19
20const Impl = if (std.builtin.single_threaded)
21 SingleThreadedCondition
22else if (std.Target.current.os.tag == .windows)
23 WindowsCondition
24else if (std.Thread.use_pthreads)
25 PthreadCondition
26else
27 AtomicCondition;
28
29pub const SingleThreadedCondition = struct {
30 pub fn wait(cond: *SingleThreadedCondition, mutex: *Mutex) void {
31 unreachable; // deadlock detected
32 }
33
34 pub fn signal(cond: *SingleThreadedCondition) void {}
35
36 pub fn broadcast(cond: *SingleThreadedCondition) void {}
37};
38
39pub const WindowsCondition = struct {
40 cond: windows.CONDITION_VARIABLE = windows.CONDITION_VARIABLE_INIT,
41
42 pub fn wait(cond: *WindowsCondition, mutex: *Mutex) void {
43 const rc = windows.kernel32.SleepConditionVariableSRW(
44 &cond.cond,
45 &mutex.srwlock,
46 windows.INFINITE,
47 @as(windows.ULONG, 0),
48 );
49 assert(rc != windows.FALSE);
50 }
51
52 pub fn signal(cond: *WindowsCondition) void {
53 windows.kernel32.WakeConditionVariable(&cond.cond);
54 }
55
56 pub fn broadcast(cond: *WindowsCondition) void {
57 windows.kernel32.WakeAllConditionVariable(&cond.cond);
58 }
59};
60
61pub const PthreadCondition = struct {
62 cond: std.c.pthread_cond_t = .{},
63
64 pub fn wait(cond: *PthreadCondition, mutex: *Mutex) void {
65 const rc = std.c.pthread_cond_wait(&cond.cond, &mutex.mutex);
66 assert(rc == 0);
67 }
68
69 pub fn signal(cond: *PthreadCondition) void {
70 const rc = std.c.pthread_cond_signal(&cond.cond);
71 assert(rc == 0);
72 }
73
74 pub fn broadcast(cond: *PthreadCondition) void {
75 const rc = std.c.pthread_cond_broadcast(&cond.cond);
76 assert(rc == 0);
77 }
78};
79
80pub const AtomicCondition = struct {
81 pending: bool = false,
82 queue_mutex: Mutex = .{},
83 queue_list: QueueList = .{},
84
85 pub const QueueList = std.SinglyLinkedList(QueueItem);
86
87 pub const QueueItem = struct {
88 futex: i32 = 0,
89
90 fn wait(cond: *@This()) void {
91 while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) {
92 switch (std.Target.current.os.tag) {
93 .linux => {
94 switch (linux.getErrno(linux.futex_wait(
95 &cond.futex,
96 linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT,
97 0,
98 null,
99 ))) {
100 0 => {},
101 std.os.EINTR => {},
102 std.os.EAGAIN => {},
103 else => unreachable,
104 }
105 },
106 else => spinLoopHint(),
107 }
108 }
109 }
110
111 fn notify(cond: *@This()) void {
112 @atomicStore(i32, &cond.futex, 1, .Release);
113
114 switch (std.Target.current.os.tag) {
115 .linux => {
116 switch (linux.getErrno(linux.futex_wake(
117 &cond.futex,
118 linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE,
119 1,
120 ))) {
121 0 => {},
122 std.os.EFAULT => {},
123 else => unreachable,
124 }
125 },
126 else => {},
127 }
128 }
129 };
130
131 pub fn wait(cond: *AtomicCondition, mutex: *Mutex) void {
132 var waiter = QueueList.Node{ .data = .{} };
133
134 {
135 const held = cond.queue_mutex.acquire();
136 defer held.release();
137
138 cond.queue_list.prepend(&waiter);
139 @atomicStore(bool, &cond.pending, true, .SeqCst);
140 }
141
142 mutex.unlock();
143 waiter.data.wait();
144 mutex.lock();
145 }
146
147 pub fn signal(cond: *AtomicCondition) void {
148 if (@atomicLoad(bool, &cond.pending, .SeqCst) == false)
149 return;
150
151 const maybe_waiter = blk: {
152 const held = cond.queue_mutex.acquire();
153 defer held.release();
154
155 const maybe_waiter = cond.queue_list.popFirst();
156 @atomicStore(bool, &cond.pending, cond.queue_list.first != null, .SeqCst);
157 break :blk maybe_waiter;
158 };
159
160 if (maybe_waiter) |waiter|
161 waiter.data.notify();
162 }
163
164 pub fn broadcast(cond: *AtomicCondition) void {
165 if (@atomicLoad(bool, &cond.pending, .SeqCst) == false)
166 return;
167
168 @atomicStore(bool, &cond.pending, false, .SeqCst);
169
170 var waiters = blk: {
171 const held = cond.queue_mutex.acquire();
172 defer held.release();
173
174 const waiters = cond.queue_list;
175 cond.queue_list = .{};
176 break :blk waiters;
177 };
178
179 while (waiters.popFirst()) |waiter|
180 waiter.data.notify();
181 }
182};
lib/std/Thread/Mutex.zig created+319
...@@ -0,0 +1,319 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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//! Lock may be held only once. If the same thread tries to acquire
8//! the same mutex twice, it deadlocks. This type supports static
9//! initialization and is at most `@sizeOf(usize)` in size. When an
10//! application is built in single threaded release mode, all the
11//! functions are no-ops. In single threaded debug mode, there is
12//! deadlock detection.
13//!
14//! Example usage:
15//! var m = Mutex{};
16//!
17//! const lock = m.acquire();
18//! defer lock.release();
19//! ... critical code
20//!
21//! Non-blocking:
22//! if (m.tryAcquire) |lock| {
23//! defer lock.release();
24//! // ... critical section
25//! } else {
26//! // ... lock not acquired
27//! }
28
29impl: Impl = .{},
30
31const Mutex = @This();
32const std = @import("../std.zig");
33const builtin = std.builtin;
34const os = std.os;
35const assert = std.debug.assert;
36const windows = os.windows;
37const linux = os.linux;
38const testing = std.testing;
39const StaticResetEvent = std.thread.StaticResetEvent;
40
41/// Try to acquire the mutex without blocking. Returns `null` if the mutex is
42/// unavailable. Otherwise returns `Held`. Call `release` on `Held`.
43pub fn tryAcquire(m: *Mutex) ?Impl.Held {
44 return m.impl.tryAcquire();
45}
46
47/// Acquire the mutex. Deadlocks if the mutex is already
48/// held by the calling thread.
49pub fn acquire(m: *Mutex) Impl.Held {
50 return m.impl.acquire();
51}
52
53const Impl = if (builtin.single_threaded)
54 Dummy
55else if (builtin.os.tag == .windows)
56 WindowsMutex
57else if (std.Thread.use_pthreads)
58 PthreadMutex
59else
60 AtomicMutex;
61
62pub const AtomicMutex = struct {
63 state: State = .unlocked,
64
65 const State = enum(i32) {
66 unlocked,
67 locked,
68 waiting,
69 };
70
71 pub const Held = struct {
72 mutex: *AtomicMutex,
73
74 pub fn release(held: Held) void {
75 switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) {
76 .unlocked => unreachable,
77 .locked => {},
78 .waiting => held.mutex.unlockSlow(),
79 }
80 }
81 };
82
83 pub fn tryAcquire(m: *AtomicMutex) ?Held {
84 if (@cmpxchgStrong(
85 State,
86 &m.state,
87 .unlocked,
88 .locked,
89 .Acquire,
90 .Monotonic,
91 ) == null) {
92 return Held{ .mutex = m };
93 } else {
94 return null;
95 }
96 }
97
98 pub fn acquire(m: *AtomicMutex) Held {
99 switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) {
100 .unlocked => {},
101 else => |s| m.lockSlow(s),
102 }
103 return Held{ .mutex = m };
104 }
105
106 fn lockSlow(m: *AtomicMutex, current_state: State) void {
107 @setCold(true);
108 var new_state = current_state;
109
110 var spin: u8 = 0;
111 while (spin < 100) : (spin += 1) {
112 const state = @cmpxchgWeak(
113 State,
114 &m.state,
115 .unlocked,
116 new_state,
117 .Acquire,
118 .Monotonic,
119 ) orelse return;
120
121 switch (state) {
122 .unlocked => {},
123 .locked => {},
124 .waiting => break,
125 }
126
127 var iter = std.math.min(32, spin + 1);
128 while (iter > 0) : (iter -= 1)
129 std.Thread.spinLoopHint();
130 }
131
132 new_state = .waiting;
133 while (true) {
134 switch (@atomicRmw(State, &m.state, .Xchg, new_state, .Acquire)) {
135 .unlocked => return,
136 else => {},
137 }
138 switch (std.Target.current.os.tag) {
139 .linux => {
140 switch (linux.getErrno(linux.futex_wait(
141 @ptrCast(*const i32, &m.state),
142 linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT,
143 @enumToInt(new_state),
144 null,
145 ))) {
146 0 => {},
147 std.os.EINTR => {},
148 std.os.EAGAIN => {},
149 else => unreachable,
150 }
151 },
152 else => std.Thread.spinLoopHint(),
153 }
154 }
155 }
156
157 fn unlockSlow(m: *AtomicMutex) void {
158 @setCold(true);
159
160 switch (std.Target.current.os.tag) {
161 .linux => {
162 switch (linux.getErrno(linux.futex_wake(
163 @ptrCast(*const i32, &m.state),
164 linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE,
165 1,
166 ))) {
167 0 => {},
168 std.os.EFAULT => {},
169 else => unreachable,
170 }
171 },
172 else => {},
173 }
174 }
175};
176
177pub const PthreadMutex = struct {
178 pthread_mutex: std.c.pthread_mutex_t = .{},
179
180 pub const Held = struct {
181 mutex: *PthreadMutex,
182
183 pub fn release(held: Held) void {
184 switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) {
185 0 => return,
186 std.c.EINVAL => unreachable,
187 std.c.EAGAIN => unreachable,
188 std.c.EPERM => unreachable,
189 else => unreachable,
190 }
191 }
192 };
193
194 /// Try to acquire the mutex without blocking. Returns null if
195 /// the mutex is unavailable. Otherwise returns Held. Call
196 /// release on Held.
197 pub fn tryAcquire(m: *PthreadMutex) ?Held {
198 if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == 0) {
199 return Held{ .mutex = m };
200 } else {
201 return null;
202 }
203 }
204
205 /// Acquire the mutex. Will deadlock if the mutex is already
206 /// held by the calling thread.
207 pub fn acquire(m: *PthreadMutex) Held {
208 switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) {
209 0 => return Held{ .mutex = m },
210 std.c.EINVAL => unreachable,
211 std.c.EBUSY => unreachable,
212 std.c.EAGAIN => unreachable,
213 std.c.EDEADLK => unreachable,
214 std.c.EPERM => unreachable,
215 else => unreachable,
216 }
217 }
218};
219
220/// This has the sematics as `Mutex`, however it does not actually do any
221/// synchronization. Operations are safety-checked no-ops.
222pub const Dummy = struct {
223 lock: @TypeOf(lock_init) = lock_init,
224
225 const lock_init = if (std.debug.runtime_safety) false else {};
226
227 pub const Held = struct {
228 mutex: *Dummy,
229
230 pub fn release(held: Held) void {
231 if (std.debug.runtime_safety) {
232 held.mutex.lock = false;
233 }
234 }
235 };
236
237 /// Try to acquire the mutex without blocking. Returns null if
238 /// the mutex is unavailable. Otherwise returns Held. Call
239 /// release on Held.
240 pub fn tryAcquire(m: *Dummy) ?Held {
241 if (std.debug.runtime_safety) {
242 if (m.lock) return null;
243 m.lock = true;
244 }
245 return Held{ .mutex = m };
246 }
247
248 /// Acquire the mutex. Will deadlock if the mutex is already
249 /// held by the calling thread.
250 pub fn acquire(m: *Dummy) Held {
251 return m.tryAcquire() orelse @panic("deadlock detected");
252 }
253};
254
255const WindowsMutex = struct {
256 srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT,
257
258 pub const Held = struct {
259 mutex: *WindowsMutex,
260
261 pub fn release(held: Held) void {
262 windows.kernel32.ReleaseSRWLockExclusive(&held.mutex.srwlock);
263 }
264 };
265
266 pub fn tryAcquire(m: *WindowsMutex) ?Held {
267 if (windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) {
268 return Held{ .mutex = m };
269 } else {
270 return null;
271 }
272 }
273
274 pub fn acquire(m: *WindowsMutex) Held {
275 windows.kernel32.AcquireSRWLockExclusive(&m.srwlock);
276 return Held{ .mutex = m };
277 }
278};
279
280const TestContext = struct {
281 mutex: *Mutex,
282 data: i128,
283
284 const incr_count = 10000;
285};
286
287test "basic usage" {
288 var mutex = Mutex{};
289
290 var context = TestContext{
291 .mutex = &mutex,
292 .data = 0,
293 };
294
295 if (builtin.single_threaded) {
296 worker(&context);
297 testing.expect(context.data == TestContext.incr_count);
298 } else {
299 const thread_count = 10;
300 var threads: [thread_count]*std.Thread = undefined;
301 for (threads) |*t| {
302 t.* = try std.Thread.spawn(&context, worker);
303 }
304 for (threads) |t|
305 t.wait();
306
307 testing.expect(context.data == thread_count * TestContext.incr_count);
308 }
309}
310
311fn worker(ctx: *TestContext) void {
312 var i: usize = 0;
313 while (i != TestContext.incr_count) : (i += 1) {
314 const held = ctx.mutex.acquire();
315 defer held.release();
316
317 ctx.data += 1;
318 }
319}
lib/std/Thread/ResetEvent.zig created+297
...@@ -0,0 +1,297 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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.Thread.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.Thread.StaticResetEvent.DebugEvent
28else if (std.Target.current.isDarwin())
29 DarwinEvent
30else if (std.Thread.use_pthreads)
31 PosixEvent
32else
33 std.Thread.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/Thread/RwLock.zig created+308
...@@ -0,0 +1,308 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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 lock that supports one writer or many readers.
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
12impl: Impl,
13
14const RwLock = @This();
15const std = @import("../std.zig");
16const builtin = std.builtin;
17const assert = std.debug.assert;
18const Mutex = std.Thread.Mutex;
19const Semaphore = std.Semaphore;
20const CondVar = std.CondVar;
21
22pub const Impl = if (builtin.single_threaded)
23 SingleThreadedRwLock
24else if (std.Thread.use_pthreads)
25 PthreadRwLock
26else
27 DefaultRwLock;
28
29pub fn init(rwl: *RwLock) void {
30 return rwl.impl.init();
31}
32
33pub fn deinit(rwl: *RwLock) void {
34 return rwl.impl.deinit();
35}
36
37/// Attempts to obtain exclusive lock ownership.
38/// Returns `true` if the lock is obtained, `false` otherwise.
39pub fn tryLock(rwl: *RwLock) bool {
40 return rwl.impl.tryLock();
41}
42
43/// Blocks until exclusive lock ownership is acquired.
44pub fn lock(rwl: *RwLock) void {
45 return rwl.impl.lock();
46}
47
48/// Releases a held exclusive lock.
49/// Asserts the lock is held exclusively.
50pub fn unlock(rwl: *RwLock) void {
51 return rwl.impl.unlock();
52}
53
54/// Attempts to obtain shared lock ownership.
55/// Returns `true` if the lock is obtained, `false` otherwise.
56pub fn tryLockShared(rwl: *RwLock) bool {
57 return rwl.impl.tryLockShared();
58}
59
60/// Blocks until shared lock ownership is acquired.
61pub fn lockShared(rwl: *RwLock) void {
62 return rwl.impl.lockShared();
63}
64
65/// Releases a held shared lock.
66pub fn unlockShared(rwl: *RwLock) void {
67 return rwl.impl.unlockShared();
68}
69
70/// Single-threaded applications use this for deadlock checks in
71/// debug mode, and no-ops in release modes.
72pub const SingleThreadedRwLock = struct {
73 state: enum { unlocked, locked_exclusive, locked_shared },
74 shared_count: usize,
75
76 pub fn init(rwl: *SingleThreadedRwLock) void {
77 rwl.* = .{
78 .state = .unlocked,
79 .shared_count = 0,
80 };
81 }
82
83 pub fn deinit(rwl: *SingleThreadedRwLock) void {
84 assert(rwl.state == .unlocked);
85 assert(rwl.shared_count == 0);
86 }
87
88 /// Attempts to obtain exclusive lock ownership.
89 /// Returns `true` if the lock is obtained, `false` otherwise.
90 pub fn tryLock(rwl: *SingleThreadedRwLock) bool {
91 switch (rwl.state) {
92 .unlocked => {
93 assert(rwl.shared_count == 0);
94 rwl.state = .locked_exclusive;
95 return true;
96 },
97 .locked_exclusive, .locked_shared => return false,
98 }
99 }
100
101 /// Blocks until exclusive lock ownership is acquired.
102 pub fn lock(rwl: *SingleThreadedRwLock) void {
103 assert(rwl.state == .unlocked); // deadlock detected
104 assert(rwl.shared_count == 0); // corrupted state detected
105 rwl.state = .locked_exclusive;
106 }
107
108 /// Releases a held exclusive lock.
109 /// Asserts the lock is held exclusively.
110 pub fn unlock(rwl: *SingleThreadedRwLock) void {
111 assert(rwl.state == .locked_exclusive);
112 assert(rwl.shared_count == 0); // corrupted state detected
113 rwl.state = .unlocked;
114 }
115
116 /// Attempts to obtain shared lock ownership.
117 /// Returns `true` if the lock is obtained, `false` otherwise.
118 pub fn tryLockShared(rwl: *SingleThreadedRwLock) bool {
119 switch (rwl.state) {
120 .unlocked => {
121 rwl.state = .locked_shared;
122 assert(rwl.shared_count == 0);
123 rwl.shared_count = 1;
124 return true;
125 },
126 .locked_exclusive, .locked_shared => return false,
127 }
128 }
129
130 /// Blocks until shared lock ownership is acquired.
131 pub fn lockShared(rwl: *SingleThreadedRwLock) void {
132 switch (rwl.state) {
133 .unlocked => {
134 rwl.state = .locked_shared;
135 assert(rwl.shared_count == 0);
136 rwl.shared_count = 1;
137 },
138 .locked_shared => {
139 rwl.shared_count += 1;
140 },
141 .locked_exclusive => unreachable, // deadlock detected
142 }
143 }
144
145 /// Releases a held shared lock.
146 pub fn unlockShared(rwl: *SingleThreadedRwLock) void {
147 switch (rwl.state) {
148 .unlocked => unreachable, // too many calls to `unlockShared`
149 .locked_exclusive => unreachable, // exclusively held lock
150 .locked_shared => {
151 rwl.shared_count -= 1;
152 if (rwl.shared_count == 0) {
153 rwl.state = .unlocked;
154 }
155 },
156 }
157 }
158};
159
160pub const PthreadRwLock = struct {
161 rwlock: pthread_rwlock_t,
162
163 pub fn init(rwl: *PthreadRwLock) void {
164 rwl.* = .{ .rwlock = .{} };
165 }
166
167 pub fn deinit(rwl: *PthreadRwLock) void {
168 const safe_rc = switch (std.builtin.os.tag) {
169 .dragonfly, .netbsd => std.os.EAGAIN,
170 else => 0,
171 };
172
173 const rc = std.c.pthread_rwlock_destroy(&rwl.rwlock);
174 assert(rc == 0 or rc == safe_rc);
175
176 rwl.* = undefined;
177 }
178
179 pub fn tryLock(rwl: *PthreadRwLock) bool {
180 return pthread_rwlock_trywrlock(&rwl.rwlock) == 0;
181 }
182
183 pub fn lock(rwl: *PthreadRwLock) void {
184 const rc = pthread_rwlock_wrlock(&rwl.rwlock);
185 assert(rc == 0);
186 }
187
188 pub fn unlock(rwl: *PthreadRwLock) void {
189 const rc = pthread_rwlock_unlock(&rwl.rwlock);
190 assert(rc == 0);
191 }
192
193 pub fn tryLockShared(rwl: *PthreadRwLock) bool {
194 return pthread_rwlock_tryrdlock(&rwl.rwlock) == 0;
195 }
196
197 pub fn lockShared(rwl: *PthreadRwLock) void {
198 const rc = pthread_rwlock_rdlock(&rwl.rwlock);
199 assert(rc == 0);
200 }
201
202 pub fn unlockShared(rwl: *PthreadRwLock) void {
203 const rc = pthread_rwlock_unlock(&rwl.rwlock);
204 assert(rc == 0);
205 }
206};
207
208pub const DefaultRwLock = struct {
209 state: usize,
210 mutex: Mutex,
211 semaphore: Semaphore,
212
213 const IS_WRITING: usize = 1;
214 const WRITER: usize = 1 << 1;
215 const READER: usize = 1 << (1 + std.meta.bitCount(Count));
216 const WRITER_MASK: usize = std.math.maxInt(Count) << @ctz(usize, WRITER);
217 const READER_MASK: usize = std.math.maxInt(Count) << @ctz(usize, READER);
218 const Count = std.meta.Int(.unsigned, @divFloor(std.meta.bitCount(usize) - 1, 2));
219
220 pub fn init(rwl: *DefaultRwLock) void {
221 rwl.* = .{
222 .state = 0,
223 .mutex = Mutex.init(),
224 .semaphore = Semaphore.init(0),
225 };
226 }
227
228 pub fn deinit(rwl: *DefaultRwLock) void {
229 rwl.semaphore.deinit();
230 rwl.mutex.deinit();
231 rwl.* = undefined;
232 }
233
234 pub fn tryLock(rwl: *DefaultRwLock) bool {
235 if (rwl.mutex.tryLock()) {
236 const state = @atomicLoad(usize, &rwl.state, .SeqCst);
237 if (state & READER_MASK == 0) {
238 _ = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .SeqCst);
239 return true;
240 }
241
242 rwl.mutex.unlock();
243 }
244
245 return false;
246 }
247
248 pub fn lock(rwl: *DefaultRwLock) void {
249 _ = @atomicRmw(usize, &rwl.state, .Add, WRITER, .SeqCst);
250 rwl.mutex.lock();
251
252 const state = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .SeqCst);
253 if (state & READER_MASK != 0)
254 rwl.semaphore.wait();
255 }
256
257 pub fn unlock(rwl: *DefaultRwLock) void {
258 _ = @atomicRmw(usize, &rwl.state, .And, ~IS_WRITING, .SeqCst);
259 rwl.mutex.unlock();
260 }
261
262 pub fn tryLockShared(rwl: *DefaultRwLock) bool {
263 const state = @atomicLoad(usize, &rwl.state, .SeqCst);
264 if (state & (IS_WRITING | WRITER_MASK) == 0) {
265 _ = @cmpxchgStrong(
266 usize,
267 &rwl.state,
268 state,
269 state + READER,
270 .SeqCst,
271 .SeqCst,
272 ) orelse return true;
273 }
274
275 if (rwl.mutex.tryLock()) {
276 _ = @atomicRmw(usize, &rwl.state, .Add, READER, .SeqCst);
277 rwl.mutex.unlock();
278 return true;
279 }
280
281 return false;
282 }
283
284 pub fn lockShared(rwl: *DefaultRwLock) void {
285 var state = @atomicLoad(usize, &rwl.state, .SeqCst);
286 while (state & (IS_WRITING | WRITER_MASK) == 0) {
287 state = @cmpxchgWeak(
288 usize,
289 &rwl.state,
290 state,
291 state + READER,
292 .SeqCst,
293 .SeqCst,
294 ) orelse return;
295 }
296
297 rwl.mutex.lock();
298 _ = @atomicRmw(usize, &rwl.state, .Add, READER, .SeqCst);
299 rwl.mutex.unlock();
300 }
301
302 pub fn unlockShared(rwl: *DefaultRwLock) void {
303 const state = @atomicRmw(usize, &rwl.state, .Sub, READER, .SeqCst);
304
305 if ((state & READER_MASK == READER) and (state & IS_WRITING != 0))
306 rwl.semaphore.post();
307 }
308};
lib/std/Thread/Semaphore.zig created+39
...@@ -0,0 +1,39 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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 semaphore is an unsigned integer that blocks the kernel thread if
8//! the number would become negative.
9//! This API supports static initialization and does not require deinitialization.
10
11mutex: Mutex = .{},
12cond: Condition = .{},
13//! It is OK to initialize this field to any value.
14permits: usize = 0,
15
16const RwLock = @This();
17const std = @import("../std.zig");
18const Mutex = std.Thread.Mutex;
19const Condition = std.Thread.Condition;
20
21pub fn wait(sem: *Semaphore) void {
22 const held = sem.mutex.acquire();
23 defer held.release();
24
25 while (sem.permits == 0)
26 sem.cond.wait(&sem.mutex);
27
28 sem.permits -= 1;
29 if (sem.permits > 0)
30 sem.cond.signal();
31}
32
33pub fn post(sem: *Semaphore) void {
34 const held = sem.mutex.acquire();
35 defer held.release();
36
37 sem.permits += 1;
38 sem.cond.signal();
39}
lib/std/Thread/StaticResetEvent.zig created+395
...@@ -0,0 +1,395 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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.Thread.ResetEvent` when possible, and use `StaticResetEvent` when
14//! the logic needs stronger API guarantees.
15
16const std = @import("../std.zig");
17const StaticResetEvent = @This();
18const assert = std.debug.assert;
19const os = std.os;
20const time = std.time;
21const linux = std.os.linux;
22const windows = std.os.windows;
23const testing = std.testing;
24
25impl: Impl = .{},
26
27pub const Impl = if (std.builtin.single_threaded)
28 DebugEvent
29else
30 AtomicEvent;
31
32/// Sets the event if not already set and wakes up all the threads waiting on
33/// the event. It is safe to call `set` multiple times before calling `wait`.
34/// However it is illegal to call `set` after `wait` is called until the event
35/// is `reset`. This function is thread-safe.
36pub fn set(ev: *StaticResetEvent) void {
37 return ev.impl.set();
38}
39
40/// Wait for the event to be set by blocking the current thread.
41/// Thread-safe. No spurious wakeups.
42/// Upon return from `wait`, the only function available to be called
43/// in `StaticResetEvent` is `reset`.
44pub fn wait(ev: *StaticResetEvent) void {
45 return ev.impl.wait();
46}
47
48/// Resets the event to its original, unset state.
49/// This function is *not* thread-safe. It is equivalent to calling
50/// `deinit` followed by `init` but without the possibility of failure.
51pub fn reset(ev: *StaticResetEvent) void {
52 return ev.impl.reset();
53}
54
55pub const TimedWaitResult = std.Thread.ResetEvent.TimedWaitResult;
56
57/// Wait for the event to be set by blocking the current thread.
58/// A timeout in nanoseconds can be provided as a hint for how
59/// long the thread should block on the unset event before returning
60/// `TimedWaitResult.timed_out`.
61/// Thread-safe. No precision of timing is guaranteed.
62/// Upon return from `timedWait`, the only function available to be called
63/// in `StaticResetEvent` is `reset`.
64pub fn timedWait(ev: *StaticResetEvent, timeout_ns: u64) TimedWaitResult {
65 return ev.impl.timedWait(timeout_ns);
66}
67
68/// For single-threaded builds, we use this to detect deadlocks.
69/// In unsafe modes this ends up being no-ops.
70pub const DebugEvent = struct {
71 state: State = State.unset,
72
73 const State = enum {
74 unset,
75 set,
76 waited,
77 };
78
79 /// This function is provided so that this type can be re-used inside
80 /// `std.Thread.ResetEvent`.
81 pub fn init(ev: *DebugEvent) void {
82 ev.* = .{};
83 }
84
85 /// This function is provided so that this type can be re-used inside
86 /// `std.Thread.ResetEvent`.
87 pub fn deinit(ev: *DebugEvent) void {
88 ev.* = undefined;
89 }
90
91 pub fn set(ev: *DebugEvent) void {
92 switch (ev.state) {
93 .unset => ev.state = .set,
94 .set => {},
95 .waited => unreachable, // Not allowed to call `set` until `reset`.
96 }
97 }
98
99 pub fn wait(ev: *DebugEvent) void {
100 switch (ev.state) {
101 .unset => unreachable, // Deadlock detected.
102 .set => return,
103 .waited => unreachable, // Not allowed to call `wait` until `reset`.
104 }
105 }
106
107 pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult {
108 switch (ev.state) {
109 .unset => return .timed_out,
110 .set => return .event_set,
111 .waited => unreachable, // Not allowed to call `wait` until `reset`.
112 }
113 }
114
115 pub fn reset(ev: *DebugEvent) void {
116 ev.state = .unset;
117 }
118};
119
120pub const AtomicEvent = struct {
121 waiters: u32 = 0,
122
123 const WAKE = 1 << 0;
124 const WAIT = 1 << 1;
125
126 /// This function is provided so that this type can be re-used inside
127 /// `std.Thread.ResetEvent`.
128 pub fn init(ev: *AtomicEvent) void {
129 ev.* = .{};
130 }
131
132 /// This function is provided so that this type can be re-used inside
133 /// `std.Thread.ResetEvent`.
134 pub fn deinit(ev: *AtomicEvent) void {
135 ev.* = undefined;
136 }
137
138 pub fn set(ev: *AtomicEvent) void {
139 const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release);
140 if (waiters >= WAIT) {
141 return Futex.wake(&ev.waiters, waiters >> 1);
142 }
143 }
144
145 pub fn wait(ev: *AtomicEvent) void {
146 switch (ev.timedWait(null)) {
147 .timed_out => unreachable,
148 .event_set => return,
149 }
150 }
151
152 pub fn timedWait(ev: *AtomicEvent, timeout: ?u64) TimedWaitResult {
153 var waiters = @atomicLoad(u32, &ev.waiters, .Acquire);
154 while (waiters != WAKE) {
155 waiters = @cmpxchgWeak(u32, &ev.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse {
156 if (Futex.wait(&ev.waiters, timeout)) |_| {
157 return .event_set;
158 } else |_| {
159 return .timed_out;
160 }
161 };
162 }
163 return .event_set;
164 }
165
166 pub fn reset(ev: *AtomicEvent) void {
167 @atomicStore(u32, &ev.waiters, 0, .Monotonic);
168 }
169
170 pub const Futex = switch (std.Target.current.os.tag) {
171 .windows => WindowsFutex,
172 .linux => LinuxFutex,
173 else => SpinFutex,
174 };
175
176 pub const SpinFutex = struct {
177 fn wake(waiters: *u32, wake_count: u32) void {}
178
179 fn wait(waiters: *u32, timeout: ?u64) !void {
180 var timer: time.Timer = undefined;
181 if (timeout != null)
182 timer = time.Timer.start() catch return error.TimedOut;
183
184 while (@atomicLoad(u32, waiters, .Acquire) != WAKE) {
185 std.os.sched_yield() catch std.Thread.spinLoopHint();
186 if (timeout) |timeout_ns| {
187 if (timer.read() >= timeout_ns)
188 return error.TimedOut;
189 }
190 }
191 }
192 };
193
194 pub const LinuxFutex = struct {
195 fn wake(waiters: *u32, wake_count: u32) void {
196 const waiting = std.math.maxInt(i32); // wake_count
197 const ptr = @ptrCast(*const i32, waiters);
198 const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting);
199 assert(linux.getErrno(rc) == 0);
200 }
201
202 fn wait(waiters: *u32, timeout: ?u64) !void {
203 var ts: linux.timespec = undefined;
204 var ts_ptr: ?*linux.timespec = null;
205 if (timeout) |timeout_ns| {
206 ts_ptr = &ts;
207 ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s);
208 ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s);
209 }
210
211 while (true) {
212 const waiting = @atomicLoad(u32, waiters, .Acquire);
213 if (waiting == WAKE)
214 return;
215 const expected = @intCast(i32, waiting);
216 const ptr = @ptrCast(*const i32, waiters);
217 const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr);
218 switch (linux.getErrno(rc)) {
219 0 => continue,
220 os.ETIMEDOUT => return error.TimedOut,
221 os.EINTR => continue,
222 os.EAGAIN => return,
223 else => unreachable,
224 }
225 }
226 }
227 };
228
229 pub const WindowsFutex = struct {
230 pub fn wake(waiters: *u32, wake_count: u32) void {
231 const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count);
232 const key = @ptrCast(*const c_void, waiters);
233
234 var waiting = wake_count;
235 while (waiting != 0) : (waiting -= 1) {
236 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
237 assert(rc == .SUCCESS);
238 }
239 }
240
241 pub fn wait(waiters: *u32, timeout: ?u64) !void {
242 const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout);
243 const key = @ptrCast(*const c_void, waiters);
244
245 // NT uses timeouts in units of 100ns with negative value being relative
246 var timeout_ptr: ?*windows.LARGE_INTEGER = null;
247 var timeout_value: windows.LARGE_INTEGER = undefined;
248 if (timeout) |timeout_ns| {
249 timeout_ptr = &timeout_value;
250 timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100);
251 }
252
253 // NtWaitForKeyedEvent doesnt have spurious wake-ups
254 var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr);
255 switch (rc) {
256 .TIMEOUT => {
257 // update the wait count to signal that we're not waiting anymore.
258 // if the .set() thread already observed that we are, perform a
259 // matching NtWaitForKeyedEvent so that the .set() thread doesn't
260 // deadlock trying to run NtReleaseKeyedEvent above.
261 var waiting = @atomicLoad(u32, waiters, .Monotonic);
262 while (true) {
263 if (waiting == WAKE) {
264 rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
265 assert(rc == .WAIT_0);
266 break;
267 } else {
268 waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break;
269 continue;
270 }
271 }
272 return error.TimedOut;
273 },
274 .WAIT_0 => {},
275 else => unreachable,
276 }
277 }
278
279 var event_handle: usize = EMPTY;
280 const EMPTY = ~@as(usize, 0);
281 const LOADING = EMPTY - 1;
282
283 pub fn getEventHandle() ?windows.HANDLE {
284 var handle = @atomicLoad(usize, &event_handle, .Monotonic);
285 while (true) {
286 switch (handle) {
287 EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse {
288 const handle_ptr = @ptrCast(*windows.HANDLE, &handle);
289 const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE;
290 if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != .SUCCESS)
291 handle = 0;
292 @atomicStore(usize, &event_handle, handle, .Monotonic);
293 return @intToPtr(?windows.HANDLE, handle);
294 },
295 LOADING => {
296 std.os.sched_yield() catch std.Thread.spinLoopHint();
297 handle = @atomicLoad(usize, &event_handle, .Monotonic);
298 },
299 else => {
300 return @intToPtr(?windows.HANDLE, handle);
301 },
302 }
303 }
304 }
305 };
306};
307
308test "basic usage" {
309 var event = StaticResetEvent{};
310
311 // test event setting
312 event.set();
313
314 // test event resetting
315 event.reset();
316
317 // test event waiting (non-blocking)
318 event.set();
319 event.wait();
320 event.reset();
321
322 event.set();
323 testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1));
324
325 // test cross-thread signaling
326 if (std.builtin.single_threaded)
327 return;
328
329 const Context = struct {
330 const Self = @This();
331
332 value: u128 = 0,
333 in: StaticResetEvent = .{},
334 out: StaticResetEvent = .{},
335
336 fn sender(self: *Self) void {
337 // update value and signal input
338 testing.expect(self.value == 0);
339 self.value = 1;
340 self.in.set();
341
342 // wait for receiver to update value and signal output
343 self.out.wait();
344 testing.expect(self.value == 2);
345
346 // update value and signal final input
347 self.value = 3;
348 self.in.set();
349 }
350
351 fn receiver(self: *Self) void {
352 // wait for sender to update value and signal input
353 self.in.wait();
354 assert(self.value == 1);
355
356 // update value and signal output
357 self.in.reset();
358 self.value = 2;
359 self.out.set();
360
361 // wait for sender to update value and signal final input
362 self.in.wait();
363 assert(self.value == 3);
364 }
365
366 fn sleeper(self: *Self) void {
367 self.in.set();
368 time.sleep(time.ns_per_ms * 2);
369 self.value = 5;
370 self.out.set();
371 }
372
373 fn timedWaiter(self: *Self) !void {
374 self.in.wait();
375 testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us));
376 try self.out.timedWait(time.ns_per_ms * 100);
377 testing.expect(self.value == 5);
378 }
379 };
380
381 var context = Context{};
382 const receiver = try std.Thread.spawn(&context, Context.receiver);
383 defer receiver.wait();
384 context.sender();
385
386 if (false) {
387 // I have now observed this fail on macOS, Windows, and Linux.
388 // https://github.com/ziglang/zig/issues/7009
389 var timed = Context.init();
390 defer timed.deinit();
391 const sleeper = try std.Thread.spawn(&timed, Context.sleeper);
392 defer sleeper.wait();
393 try timed.timedWaiter();
394 }
395}
lib/std/atomic/queue.zig+2-2
...@@ -16,7 +16,7 @@ pub fn Queue(comptime T: type) type {...@@ -16,7 +16,7 @@ pub fn Queue(comptime T: type) type {
16 return struct {16 return struct {
17 head: ?*Node,17 head: ?*Node,
18 tail: ?*Node,18 tail: ?*Node,
19 mutex: std.Mutex,19 mutex: std.Thread.Mutex,
2020
21 pub const Self = @This();21 pub const Self = @This();
22 pub const Node = std.TailQueue(T).Node;22 pub const Node = std.TailQueue(T).Node;
...@@ -27,7 +27,7 @@ pub fn Queue(comptime T: type) type {...@@ -27,7 +27,7 @@ pub fn Queue(comptime T: type) type {
27 return Self{27 return Self{
28 .head = null,28 .head = null,
29 .tail = null,29 .tail = null,
30 .mutex = std.Mutex{},30 .mutex = std.Thread.Mutex{},
31 };31 };
32 }32 }
3333
lib/std/auto_reset_event.zig deleted-226
...@@ -1,226 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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 assert = std.debug.assert;
10const StaticResetEvent = std.StaticResetEvent;
11
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).
14pub const AutoResetEvent = struct {
15 /// AutoResetEvent has 3 possible states:
16 /// - UNSET: the AutoResetEvent is currently unset
17 /// - SET: the AutoResetEvent was notified before a wait() was called
18 /// - <StaticResetEvent pointer>: there is an active waiter waiting for a notification.
19 ///
20 /// When attempting to wait:
21 /// if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
22 /// if the event is already set, then it consumes the notification and resets the event.
23 ///
24 /// When attempting to notify:
25 /// if the event is unset, then we set the event
26 /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
27 ///
28 /// This ensures that the event is automatically reset after a wait() has been issued
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)
36 state: usize = UNSET,
37
38 const UNSET = 0;
39 const SET = 1;
40
41 /// the minimum alignment for the `*StaticResetEvent` created by wait*()
42 const event_align = std.math.max(@alignOf(StaticResetEvent), 2);
43
44 pub fn wait(self: *AutoResetEvent) void {
45 self.waitFor(null) catch unreachable;
46 }
47
48 pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void {
49 return self.waitFor(timeout);
50 }
51
52 fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void {
53 // lazily initialized StaticResetEvent
54 var reset_event: StaticResetEvent align(event_align) = undefined;
55 var has_reset_event = false;
56
57 var state = @atomicLoad(usize, &self.state, .SeqCst);
58 while (true) {
59 // consume a notification if there is any
60 if (state == SET) {
61 @atomicStore(usize, &self.state, UNSET, .SeqCst);
62 return;
63 }
64
65 // check if theres currently a pending ResetEvent pointer already registered
66 if (state != UNSET) {
67 unreachable; // multiple waiting threads on the same AutoResetEvent
68 }
69
70 // lazily initialize the ResetEvent if it hasn't been already
71 if (!has_reset_event) {
72 has_reset_event = true;
73 reset_event = .{};
74 }
75
76 // Since the AutoResetEvent currently isnt set,
77 // try to register our ResetEvent on it to wait
78 // for a set() call from another thread.
79 if (@cmpxchgWeak(
80 usize,
81 &self.state,
82 UNSET,
83 @ptrToInt(&reset_event),
84 .SeqCst,
85 .SeqCst,
86 )) |new_state| {
87 state = new_state;
88 continue;
89 }
90
91 // if no timeout was specified, then just wait forever
92 const timeout_ns = timeout orelse {
93 reset_event.wait();
94 return;
95 };
96
97 // wait with a timeout and return if signalled via set()
98 switch (reset_event.timedWait(timeout_ns)) {
99 .event_set => return,
100 .timed_out => {},
101 }
102
103 // If we timed out, we need to transition the AutoResetEvent back to UNSET.
104 // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent.
105 state = @cmpxchgStrong(
106 usize,
107 &self.state,
108 @ptrToInt(&reset_event),
109 UNSET,
110 .SeqCst,
111 .SeqCst,
112 ) orelse return error.TimedOut;
113
114 // We didn't manage to unregister ourselves from the state.
115 if (state == SET) {
116 unreachable; // AutoResetEvent notified without waking up the waiting thread
117 } else if (state != UNSET) {
118 unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out
119 }
120
121 // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up.
122 // We need to wait for it to wake up our ResetEvent before we can return and invalidate it.
123 // We don't return error.TimedOut here as it technically notified us while we were "timing out".
124 reset_event.wait();
125 return;
126 }
127 }
128
129 pub fn set(self: *AutoResetEvent) void {
130 var state = @atomicLoad(usize, &self.state, .SeqCst);
131 while (true) {
132 // If the AutoResetEvent is already set, there is nothing else left to do
133 if (state == SET) {
134 return;
135 }
136
137 // If the AutoResetEvent isn't set,
138 // then try to leave a notification for the wait() thread that we set() it.
139 if (state == UNSET) {
140 state = @cmpxchgWeak(
141 usize,
142 &self.state,
143 UNSET,
144 SET,
145 .SeqCst,
146 .SeqCst,
147 ) orelse return;
148 continue;
149 }
150
151 // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting.
152 // Try to acquire ownership of it so that we can wake it up.
153 // This also resets the AutoResetEvent so that there is no race condition as defined above.
154 if (@cmpxchgWeak(
155 usize,
156 &self.state,
157 state,
158 UNSET,
159 .SeqCst,
160 .SeqCst,
161 )) |new_state| {
162 state = new_state;
163 continue;
164 }
165
166 const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state);
167 reset_event.set();
168 return;
169 }
170 }
171};
172
173test "std.AutoResetEvent" {
174 // test local code paths
175 {
176 var event = AutoResetEvent{};
177 testing.expectError(error.TimedOut, event.timedWait(1));
178 event.set();
179 event.wait();
180 }
181
182 // test cross-thread signaling
183 if (builtin.single_threaded)
184 return;
185
186 const Context = struct {
187 value: u128 = 0,
188 in: AutoResetEvent = AutoResetEvent{},
189 out: AutoResetEvent = AutoResetEvent{},
190
191 const Self = @This();
192
193 fn sender(self: *Self) void {
194 testing.expect(self.value == 0);
195 self.value = 1;
196 self.out.set();
197
198 self.in.wait();
199 testing.expect(self.value == 2);
200 self.value = 3;
201 self.out.set();
202
203 self.in.wait();
204 testing.expect(self.value == 4);
205 }
206
207 fn receiver(self: *Self) void {
208 self.out.wait();
209 testing.expect(self.value == 1);
210 self.value = 2;
211 self.in.set();
212
213 self.out.wait();
214 testing.expect(self.value == 3);
215 self.value = 4;
216 self.in.set();
217 }
218 };
219
220 var context = Context{};
221 const send_thread = try std.Thread.spawn(&context, Context.sender);
222 const recv_thread = try std.Thread.spawn(&context, Context.receiver);
223
224 send_thread.wait();
225 recv_thread.wait();
226}
lib/std/c.zig+7
...@@ -338,6 +338,13 @@ pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) c_int;...@@ -338,6 +338,13 @@ pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) c_int;
338pub extern "c" fn pthread_cond_broadcast(cond: *pthread_cond_t) c_int;338pub extern "c" fn pthread_cond_broadcast(cond: *pthread_cond_t) c_int;
339pub extern "c" fn pthread_cond_destroy(cond: *pthread_cond_t) c_int;339pub extern "c" fn pthread_cond_destroy(cond: *pthread_cond_t) c_int;
340340
341pub extern "c" fn pthread_rwlock_destroy(rwl: *pthread_rwlock_t) callconv(.C) c_int;
342pub extern "c" fn pthread_rwlock_rdlock(rwl: *pthread_rwlock_t) callconv(.C) c_int;
343pub extern "c" fn pthread_rwlock_wrlock(rwl: *pthread_rwlock_t) callconv(.C) c_int;
344pub extern "c" fn pthread_rwlock_tryrdlock(rwl: *pthread_rwlock_t) callconv(.C) c_int;
345pub extern "c" fn pthread_rwlock_trywrlock(rwl: *pthread_rwlock_t) callconv(.C) c_int;
346pub extern "c" fn pthread_rwlock_unlock(rwl: *pthread_rwlock_t) callconv(.C) c_int;
347
341pub const pthread_t = *opaque {};348pub const pthread_t = *opaque {};
342pub const FILE = opaque {};349pub const FILE = opaque {};
343350
lib/std/c/darwin.zig+5-1
...@@ -177,6 +177,10 @@ pub const pthread_cond_t = extern struct {...@@ -177,6 +177,10 @@ pub const pthread_cond_t = extern struct {
177 __sig: c_long = 0x3CB0B1BB,177 __sig: c_long = 0x3CB0B1BB,
178 __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__,178 __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__,
179};179};
180pub const pthread_rwlock_t = extern struct {
181 __sig: c_long = 0x2DA8B3B4,
182 __opaque: [192]u8 = [_]u8{0} ** 192,
183};
180pub const sem_t = c_int;184pub const sem_t = c_int;
181const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40;185const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40;
182const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24;186const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24;
...@@ -192,7 +196,7 @@ pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int...@@ -192,7 +196,7 @@ pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int
192pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;196pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;
193197
194// Grand Central Dispatch is exposed by libSystem.198// Grand Central Dispatch is exposed by libSystem.
195pub const dispatch_semaphore_t = *opaque{};199pub const dispatch_semaphore_t = *opaque {};
196pub const dispatch_time_t = u64;200pub const dispatch_time_t = u64;
197pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0);201pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0);
198pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0);202pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0);
lib/std/c/emscripten.zig+3
...@@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct {...@@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct {
9pub const pthread_cond_t = extern struct {9pub const pthread_cond_t = extern struct {
10 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,10 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
11};11};
12pub const pthread_rwlock_t = extern struct {
13 size: [32]u8 align(4) = [_]u8{0} ** 32,
14};
12const __SIZEOF_PTHREAD_COND_T = 48;15const __SIZEOF_PTHREAD_COND_T = 48;
13const __SIZEOF_PTHREAD_MUTEX_T = 28;16const __SIZEOF_PTHREAD_MUTEX_T = 28;
lib/std/c/freebsd.zig+3
...@@ -43,6 +43,9 @@ pub const pthread_mutex_t = extern struct {...@@ -43,6 +43,9 @@ pub const pthread_mutex_t = extern struct {
43pub const pthread_cond_t = extern struct {43pub const pthread_cond_t = extern struct {
44 inner: ?*c_void = null,44 inner: ?*c_void = null,
45};45};
46pub const pthread_rwlock_t = extern struct {
47 ptr: ?*c_void = null,
48};
4649
47pub const pthread_attr_t = extern struct {50pub const pthread_attr_t = extern struct {
48 __size: [56]u8,51 __size: [56]u8,
lib/std/c/fuchsia.zig+3
...@@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct {...@@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct {
9pub const pthread_cond_t = extern struct {9pub const pthread_cond_t = extern struct {
10 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,10 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
11};11};
12pub const pthread_rwlock_t = extern struct {
13 size: [56]u8 align(@alignOf(usize)) = [_]u8{0} ** 56,
14};
12const __SIZEOF_PTHREAD_COND_T = 48;15const __SIZEOF_PTHREAD_COND_T = 48;
13const __SIZEOF_PTHREAD_MUTEX_T = 40;16const __SIZEOF_PTHREAD_MUTEX_T = 40;
lib/std/c/haiku.zig+9
...@@ -17,3 +17,12 @@ pub const pthread_cond_t = extern struct {...@@ -17,3 +17,12 @@ pub const pthread_cond_t = extern struct {
17 waiter_count: i32 = 0,17 waiter_count: i32 = 0,
18 lock: i32 = 0,18 lock: i32 = 0,
19};19};
20pub const pthread_rwlock_t = extern struct {
21 flags: u32 = 0,
22 owner: i32 = -1,
23 lock_sem: i32 = 0,
24 lock_count: i32 = 0,
25 reader_count: i32 = 0,
26 writer_count: i32 = 0,
27 waiters: [2]?*c_void = [_]?*c_void{ null, null },
28};
lib/std/c/hermit.zig+3
...@@ -9,3 +9,6 @@ pub const pthread_mutex_t = extern struct {...@@ -9,3 +9,6 @@ pub const pthread_mutex_t = extern struct {
9pub const pthread_cond_t = extern struct {9pub const pthread_cond_t = extern struct {
10 inner: usize = ~@as(usize, 0),10 inner: usize = ~@as(usize, 0),
11};11};
12pub const pthread_rwlock_t = extern struct {
13 ptr: usize = std.math.maxInt(usize),
14};
lib/std/c/linux.zig+26
...@@ -123,6 +123,32 @@ pub const pthread_mutex_t = extern struct {...@@ -123,6 +123,32 @@ pub const pthread_mutex_t = extern struct {
123pub const pthread_cond_t = extern struct {123pub const pthread_cond_t = extern struct {
124 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,124 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
125};125};
126pub const pthread_rwlock_t = switch (std.builtin.abi) {
127 .android => switch (@sizeOf(usize)) {
128 4 => extern struct {
129 lock: std.c.pthread_mutex_t = std.c.PTHREAD_MUTEX_INITIALIZER,
130 cond: std.c.pthread_cond_t = std.c.PTHREAD_COND_INITIALIZER,
131 numLocks: c_int = 0,
132 writerThreadId: c_int = 0,
133 pendingReaders: c_int = 0,
134 pendingWriters: c_int = 0,
135 attr: i32 = 0,
136 __reserved: [12]u8 = [_]u8{0} ** 2,
137 },
138 8 => extern struct {
139 numLocks: c_int = 0,
140 writerThreadId: c_int = 0,
141 pendingReaders: c_int = 0,
142 pendingWriters: c_int = 0,
143 attr: i32 = 0,
144 __reserved: [36]u8 = [_]u8{0} ** 36,
145 },
146 else => unreachable,
147 },
148 else => extern struct {
149 size: [56]u8 align(@alignOf(usize)) = [_]u8{0} ** 56,
150 },
151};
126pub const sem_t = extern struct {152pub const sem_t = extern struct {
127 __size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)),153 __size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)),
128};154};
lib/std/c/netbsd.zig+16
...@@ -54,6 +54,22 @@ pub const pthread_cond_t = extern struct {...@@ -54,6 +54,22 @@ pub const pthread_cond_t = extern struct {
54 ptc_private: ?*c_void = null,54 ptc_private: ?*c_void = null,
55};55};
5656
57pub const pthread_rwlock_t = extern struct {
58 ptr_magic: c_uint = 0x99990009,
59 ptr_interlock: switch (std.builtin.arch) {
60 .aarch64, .sparc, .x86_64, .i386 => u8,
61 .arm, .powerpc => c_int,
62 else => unreachable,
63 } = 0,
64 ptr_rblocked_first: ?*u8 = null,
65 ptr_rblocked_last: ?*u8 = null,
66 ptr_wblocked_first: ?*u8 = null,
67 ptr_wblocked_last: ?*u8 = null,
68 ptr_nreaders: c_uint = 0,
69 ptr_owner: std.c.pthread_t = null,
70 ptr_private: ?*c_void = null,
71};
72
57const pthread_spin_t = switch (builtin.arch) {73const pthread_spin_t = switch (builtin.arch) {
58 .aarch64, .aarch64_be, .aarch64_32 => u8,74 .aarch64, .aarch64_be, .aarch64_32 => u8,
59 .mips, .mipsel, .mips64, .mips64el => u32,75 .mips, .mipsel, .mips64, .mips64el => u32,
lib/std/c/openbsd.zig+3
...@@ -27,6 +27,9 @@ pub const pthread_mutex_t = extern struct {...@@ -27,6 +27,9 @@ pub const pthread_mutex_t = extern struct {
27pub const pthread_cond_t = extern struct {27pub const pthread_cond_t = extern struct {
28 inner: ?*c_void = null,28 inner: ?*c_void = null,
29};29};
30pub const pthread_rwlock_t = extern struct {
31 ptr: ?*c_void = null,
32};
30pub const pthread_spinlock_t = extern struct {33pub const pthread_spinlock_t = extern struct {
31 inner: ?*c_void = null,34 inner: ?*c_void = null,
32};35};
lib/std/debug.zig+4-4
...@@ -50,7 +50,7 @@ pub const LineInfo = struct {...@@ -50,7 +50,7 @@ pub const LineInfo = struct {
50 }50 }
51};51};
5252
53var stderr_mutex = std.Mutex{};53var stderr_mutex = std.Thread.Mutex{};
5454
55/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for55/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for
56/// "printf debugging".56/// "printf debugging".
...@@ -65,7 +65,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {...@@ -65,7 +65,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
65 nosuspend stderr.print(fmt, args) catch return;65 nosuspend stderr.print(fmt, args) catch return;
66}66}
6767
68pub fn getStderrMutex() *std.Mutex {68pub fn getStderrMutex() *std.Thread.Mutex {
69 return &stderr_mutex;69 return &stderr_mutex;
70}70}
7171
...@@ -235,7 +235,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {...@@ -235,7 +235,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {
235var panicking: u8 = 0;235var panicking: u8 = 0;
236236
237// Locked to avoid interleaving panic messages from multiple threads.237// Locked to avoid interleaving panic messages from multiple threads.
238var panic_mutex = std.Mutex{};238var panic_mutex = std.Thread.Mutex{};
239239
240/// Counts how many times the panic handler is invoked by this thread.240/// Counts how many times the panic handler is invoked by this thread.
241/// This is used to catch and handle panics triggered by the panic handler.241/// This is used to catch and handle panics triggered by the panic handler.
...@@ -280,7 +280,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c...@@ -280,7 +280,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
280 // and call abort()280 // and call abort()
281281
282 // Sleep forever without hammering the CPU282 // Sleep forever without hammering the CPU
283 var event: std.StaticResetEvent = .{};283 var event: std.Thread.StaticResetEvent = .{};
284 event.wait();284 event.wait();
285 unreachable;285 unreachable;
286 }286 }
lib/std/event/lock.zig+1-1
...@@ -16,7 +16,7 @@ const Loop = std.event.Loop;...@@ -16,7 +16,7 @@ const Loop = std.event.Loop;
16/// Allows only one actor to hold the lock.16/// Allows only one actor to hold the lock.
17/// TODO: make this API also work in blocking I/O mode.17/// TODO: make this API also work in blocking I/O mode.
18pub const Lock = struct {18pub const Lock = struct {
19 mutex: std.Mutex = std.Mutex{},19 mutex: std.Thread.Mutex = std.Thread.Mutex{},
20 head: usize = UNLOCKED,20 head: usize = UNLOCKED,
2121
22 const UNLOCKED = 0;22 const UNLOCKED = 0;
lib/std/event/loop.zig+3-3
...@@ -29,7 +29,7 @@ pub const Loop = struct {...@@ -29,7 +29,7 @@ pub const Loop = struct {
29 fs_thread: *Thread,29 fs_thread: *Thread,
30 fs_queue: std.atomic.Queue(Request),30 fs_queue: std.atomic.Queue(Request),
31 fs_end_request: Request.Node,31 fs_end_request: Request.Node,
32 fs_thread_wakeup: std.ResetEvent,32 fs_thread_wakeup: std.Thread.ResetEvent,
3333
34 /// For resources that have the same lifetime as the `Loop`.34 /// For resources that have the same lifetime as the `Loop`.
35 /// This is only used by `Loop` for the thread pool and associated resources.35 /// This is only used by `Loop` for the thread pool and associated resources.
...@@ -785,7 +785,7 @@ pub const Loop = struct {...@@ -785,7 +785,7 @@ pub const Loop = struct {
785 timer: std.time.Timer,785 timer: std.time.Timer,
786 waiters: Waiters,786 waiters: Waiters,
787 thread: *std.Thread,787 thread: *std.Thread,
788 event: std.AutoResetEvent,788 event: std.Thread.AutoResetEvent,
789 is_running: bool,789 is_running: bool,
790790
791 /// Initialize the delay queue by spawning the timer thread791 /// Initialize the delay queue by spawning the timer thread
...@@ -796,7 +796,7 @@ pub const Loop = struct {...@@ -796,7 +796,7 @@ pub const Loop = struct {
796 .waiters = DelayQueue.Waiters{796 .waiters = DelayQueue.Waiters{
797 .entries = std.atomic.Queue(anyframe).init(),797 .entries = std.atomic.Queue(anyframe).init(),
798 },798 },
799 .event = std.AutoResetEvent{},799 .event = std.Thread.AutoResetEvent{},
800 .is_running = true,800 .is_running = true,
801 // Must be last so that it can read the other state, such as `is_running`.801 // Must be last so that it can read the other state, such as `is_running`.
802 .thread = try std.Thread.spawn(self, DelayQueue.run),802 .thread = try std.Thread.spawn(self, DelayQueue.run),
lib/std/event/wait_group.zig+1-1
...@@ -30,7 +30,7 @@ pub fn WaitGroupGeneric(comptime counter_size: u16) type {...@@ -30,7 +30,7 @@ pub fn WaitGroupGeneric(comptime counter_size: u16) type {
30 return struct {30 return struct {
31 counter: CounterType = 0,31 counter: CounterType = 0,
32 max_counter: CounterType = std.math.maxInt(CounterType),32 max_counter: CounterType = std.math.maxInt(CounterType),
33 mutex: std.Mutex = .{},33 mutex: std.Thread.Mutex = .{},
34 waiters: ?*Waiter = null,34 waiters: ?*Waiter = null,
35 const Waiter = struct {35 const Waiter = struct {
36 next: ?*Waiter,36 next: ?*Waiter,
lib/std/fs/test.zig+2-2
...@@ -750,7 +750,7 @@ test "open file with exclusive lock twice, make sure it waits" {...@@ -750,7 +750,7 @@ test "open file with exclusive lock twice, make sure it waits" {
750 errdefer file.close();750 errdefer file.close();
751751
752 const S = struct {752 const S = struct {
753 const C = struct { dir: *fs.Dir, evt: *std.ResetEvent };753 const C = struct { dir: *fs.Dir, evt: *std.Thread.ResetEvent };
754 fn checkFn(ctx: C) !void {754 fn checkFn(ctx: C) !void {
755 const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive });755 const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive });
756 defer file1.close();756 defer file1.close();
...@@ -758,7 +758,7 @@ test "open file with exclusive lock twice, make sure it waits" {...@@ -758,7 +758,7 @@ test "open file with exclusive lock twice, make sure it waits" {
758 }758 }
759 };759 };
760760
761 var evt: std.ResetEvent = undefined;761 var evt: std.Thread.ResetEvent = undefined;
762 try evt.init();762 try evt.init();
763 defer evt.deinit();763 defer evt.deinit();
764764
lib/std/heap/general_purpose_allocator.zig+8-8
...@@ -149,13 +149,13 @@ pub const Config = struct {...@@ -149,13 +149,13 @@ pub const Config = struct {
149 thread_safe: bool = !std.builtin.single_threaded,149 thread_safe: bool = !std.builtin.single_threaded,
150150
151 /// What type of mutex you'd like to use, for thread safety.151 /// What type of mutex you'd like to use, for thread safety.
152 /// when specfied, the mutex type must have the same shape as `std.Mutex` and152 /// when specfied, the mutex type must have the same shape as `std.Thread.Mutex` and
153 /// `std.mutex.Dummy`, and have no required fields. Specifying this field causes153 /// `std.Thread.Mutex.Dummy`, and have no required fields. Specifying this field causes
154 /// the `thread_safe` field to be ignored.154 /// the `thread_safe` field to be ignored.
155 ///155 ///
156 /// when null (default):156 /// when null (default):
157 /// * the mutex type defaults to `std.Mutex` when thread_safe is enabled.157 /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled.
158 /// * the mutex type defaults to `std.mutex.Dummy` otherwise.158 /// * the mutex type defaults to `std.Thread.Mutex.Dummy` otherwise.
159 MutexType: ?type = null,159 MutexType: ?type = null,
160160
161 /// This is a temporary debugging trick you can use to turn segfaults into more helpful161 /// This is a temporary debugging trick you can use to turn segfaults into more helpful
...@@ -187,9 +187,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -187,9 +187,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
187 const mutex_init = if (config.MutexType) |T|187 const mutex_init = if (config.MutexType) |T|
188 T{}188 T{}
189 else if (config.thread_safe)189 else if (config.thread_safe)
190 std.Mutex{}190 std.Thread.Mutex{}
191 else191 else
192 std.mutex.Dummy{};192 std.Thread.Mutex.Dummy{};
193193
194 const stack_n = config.stack_trace_frames;194 const stack_n = config.stack_trace_frames;
195 const one_trace_size = @sizeOf(usize) * stack_n;195 const one_trace_size = @sizeOf(usize) * stack_n;
...@@ -869,9 +869,9 @@ test "realloc large object to small object" {...@@ -869,9 +869,9 @@ test "realloc large object to small object" {
869}869}
870870
871test "overrideable mutexes" {871test "overrideable mutexes" {
872 var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Mutex }){872 var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Thread.Mutex }){
873 .backing_allocator = std.testing.allocator,873 .backing_allocator = std.testing.allocator,
874 .mutex = std.Mutex{},874 .mutex = std.Thread.Mutex{},
875 };875 };
876 defer std.testing.expect(!gpa.deinit());876 defer std.testing.expect(!gpa.deinit());
877 const allocator = &gpa.allocator;877 const allocator = &gpa.allocator;
lib/std/mutex.zig deleted-379
...@@ -1,379 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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 os = std.os;
9const assert = std.debug.assert;
10const windows = os.windows;
11const testing = std.testing;
12const SpinLock = std.SpinLock;
13const StaticResetEvent = std.StaticResetEvent;
14
15/// Lock may be held only once. If the same thread tries to acquire
16/// the same mutex twice, it deadlocks. This type supports static
17/// initialization and is at most `@sizeOf(usize)` in size. When an
18/// application is built in single threaded release mode, all the
19/// functions are no-ops. In single threaded debug mode, there is
20/// deadlock detection.
21///
22/// Example usage:
23/// var m = Mutex{};
24///
25/// const lock = m.acquire();
26/// defer lock.release();
27/// ... critical code
28///
29/// Non-blocking:
30/// if (m.tryAcquire) |lock| {
31/// defer lock.release();
32/// // ... critical section
33/// } else {
34/// // ... lock not acquired
35/// }
36pub const Mutex = if (builtin.single_threaded)
37 Dummy
38else if (builtin.os.tag == .windows)
39 WindowsMutex
40else if (std.Thread.use_pthreads)
41 PthreadMutex
42else if (builtin.link_libc or builtin.os.tag == .linux)
43 // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs
44 struct {
45 state: usize = 0,
46
47 /// number of times to spin trying to acquire the lock.
48 /// https://webkit.org/blog/6161/locking-in-webkit/
49 const SPIN_COUNT = 40;
50
51 const MUTEX_LOCK: usize = 1 << 0;
52 const QUEUE_LOCK: usize = 1 << 1;
53 const QUEUE_MASK: usize = ~(MUTEX_LOCK | QUEUE_LOCK);
54
55 const Node = struct {
56 next: ?*Node,
57 event: StaticResetEvent,
58 };
59
60 pub fn tryAcquire(self: *Mutex) ?Held {
61 if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)
62 return null;
63 return Held{ .mutex = self };
64 }
65
66 pub fn acquire(self: *Mutex) Held {
67 return self.tryAcquire() orelse {
68 self.acquireSlow();
69 return Held{ .mutex = self };
70 };
71 }
72
73 fn acquireSlow(self: *Mutex) void {
74 // inlining the fast path and hiding *Slow()
75 // calls behind a @setCold(true) appears to
76 // improve performance in release builds.
77 @setCold(true);
78 while (true) {
79
80 // try and spin for a bit to acquire the mutex if theres currently no queue
81 var spin_count: u32 = SPIN_COUNT;
82 var state = @atomicLoad(usize, &self.state, .Monotonic);
83 while (spin_count != 0) : (spin_count -= 1) {
84 if (state & MUTEX_LOCK == 0) {
85 _ = @cmpxchgWeak(usize, &self.state, state, state | MUTEX_LOCK, .Acquire, .Monotonic) orelse return;
86 } else if (state & QUEUE_MASK == 0) {
87 break;
88 }
89 SpinLock.yield();
90 state = @atomicLoad(usize, &self.state, .Monotonic);
91 }
92
93 // create the StaticResetEvent node on the stack
94 // (faster than threadlocal on platforms like OSX)
95 var node: Node = .{
96 .next = undefined,
97 .event = .{},
98 };
99
100 // we've spun too long, try and add our node to the LIFO queue.
101 // if the mutex becomes available in the process, try and grab it instead.
102 while (true) {
103 if (state & MUTEX_LOCK == 0) {
104 _ = @cmpxchgWeak(usize, &self.state, state, state | MUTEX_LOCK, .Acquire, .Monotonic) orelse return;
105 } else {
106 node.next = @intToPtr(?*Node, state & QUEUE_MASK);
107 const new_state = @ptrToInt(&node) | (state & ~QUEUE_MASK);
108 _ = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Monotonic) orelse {
109 node.event.wait();
110 break;
111 };
112 }
113 SpinLock.yield();
114 state = @atomicLoad(usize, &self.state, .Monotonic);
115 }
116 }
117 }
118
119 /// Returned when the lock is acquired. Call release to
120 /// release.
121 pub const Held = struct {
122 mutex: *Mutex,
123
124 /// Release the held lock.
125 pub fn release(self: Held) void {
126 // first, remove the lock bit so another possibly parallel acquire() can succeed.
127 // use .Sub since it can be usually compiled down more efficiency
128 // (`lock sub` on x86) vs .And ~MUTEX_LOCK (`lock cmpxchg` loop on x86)
129 const state = @atomicRmw(usize, &self.mutex.state, .Sub, MUTEX_LOCK, .Release);
130
131 // if the LIFO queue isnt locked and it has a node, try and wake up the node.
132 if ((state & QUEUE_LOCK) == 0 and (state & QUEUE_MASK) != 0)
133 self.mutex.releaseSlow();
134 }
135 };
136
137 fn releaseSlow(self: *Mutex) void {
138 @setCold(true);
139
140 // try and lock the LFIO queue to pop a node off,
141 // stopping altogether if its already locked or the queue is empty
142 var state = @atomicLoad(usize, &self.state, .Monotonic);
143 while (true) : (SpinLock.loopHint(1)) {
144 if (state & QUEUE_LOCK != 0 or state & QUEUE_MASK == 0)
145 return;
146 state = @cmpxchgWeak(usize, &self.state, state, state | QUEUE_LOCK, .Acquire, .Monotonic) orelse break;
147 }
148
149 // acquired the QUEUE_LOCK, try and pop a node to wake it.
150 // if the mutex is locked, then unset QUEUE_LOCK and let
151 // the thread who holds the mutex do the wake-up on unlock()
152 while (true) : (SpinLock.loopHint(1)) {
153 if ((state & MUTEX_LOCK) != 0) {
154 state = @cmpxchgWeak(usize, &self.state, state, state & ~QUEUE_LOCK, .Release, .Acquire) orelse return;
155 } else {
156 const node = @intToPtr(*Node, state & QUEUE_MASK);
157 const new_state = @ptrToInt(node.next);
158 state = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Acquire) orelse {
159 node.event.set();
160 return;
161 };
162 }
163 }
164 }
165 }
166
167 // for platforms without a known OS blocking
168 // primitive, default to SpinLock for correctness
169else
170 SpinLock;
171
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
218/// This has the sematics as `Mutex`, however it does not actually do any
219/// synchronization. Operations are safety-checked no-ops.
220pub const Dummy = struct {
221 lock: @TypeOf(lock_init) = lock_init,
222
223 const lock_init = if (std.debug.runtime_safety) false else {};
224
225 pub const Held = struct {
226 mutex: *Dummy,
227
228 pub fn release(self: Held) void {
229 if (std.debug.runtime_safety) {
230 self.mutex.lock = false;
231 }
232 }
233 };
234
235 /// Create a new mutex in unlocked state.
236 pub const init = Dummy{};
237
238 /// Try to acquire the mutex without blocking. Returns null if
239 /// the mutex is unavailable. Otherwise returns Held. Call
240 /// release on Held.
241 pub fn tryAcquire(self: *Dummy) ?Held {
242 if (std.debug.runtime_safety) {
243 if (self.lock) return null;
244 self.lock = true;
245 }
246 return Held{ .mutex = self };
247 }
248
249 /// Acquire the mutex. Will deadlock if the mutex is already
250 /// held by the calling thread.
251 pub fn acquire(self: *Dummy) Held {
252 return self.tryAcquire() orelse @panic("deadlock detected");
253 }
254};
255
256// https://locklessinc.com/articles/keyed_events/
257const WindowsMutex = struct {
258 state: State = State{ .waiters = 0 },
259
260 const State = extern union {
261 locked: u8,
262 waiters: u32,
263 };
264
265 const WAKE = 1 << 8;
266 const WAIT = 1 << 9;
267
268 pub fn tryAcquire(self: *WindowsMutex) ?Held {
269 if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) != 0)
270 return null;
271 return Held{ .mutex = self };
272 }
273
274 pub fn acquire(self: *WindowsMutex) Held {
275 return self.tryAcquire() orelse self.acquireSlow();
276 }
277
278 fn acquireSpinning(self: *WindowsMutex) Held {
279 @setCold(true);
280 while (true) : (SpinLock.yield()) {
281 return self.tryAcquire() orelse continue;
282 }
283 }
284
285 fn acquireSlow(self: *WindowsMutex) Held {
286 // try to use NT keyed events for blocking, falling back to spinlock if unavailable
287 @setCold(true);
288 const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return self.acquireSpinning();
289 const key = @ptrCast(*const c_void, &self.state.waiters);
290
291 while (true) : (SpinLock.loopHint(1)) {
292 const waiters = @atomicLoad(u32, &self.state.waiters, .Monotonic);
293
294 // try and take lock if unlocked
295 if ((waiters & 1) == 0) {
296 if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) == 0) {
297 return Held{ .mutex = self };
298 }
299
300 // otherwise, try and update the waiting count.
301 // then unset the WAKE bit so that another unlocker can wake up a thread.
302 } else if (@cmpxchgWeak(u32, &self.state.waiters, waiters, (waiters + WAIT) | 1, .Monotonic, .Monotonic) == null) {
303 const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
304 assert(rc == .SUCCESS);
305 _ = @atomicRmw(u32, &self.state.waiters, .Sub, WAKE, .Monotonic);
306 }
307 }
308 }
309
310 pub const Held = struct {
311 mutex: *WindowsMutex,
312
313 pub fn release(self: Held) void {
314 // unlock without a rmw/cmpxchg instruction
315 @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release);
316 const handle = StaticResetEvent.Impl.Futex.getEventHandle() orelse return;
317 const key = @ptrCast(*const c_void, &self.mutex.state.waiters);
318
319 while (true) : (SpinLock.loopHint(1)) {
320 const waiters = @atomicLoad(u32, &self.mutex.state.waiters, .Monotonic);
321
322 // no one is waiting
323 if (waiters < WAIT) return;
324 // someone grabbed the lock and will do the wake instead
325 if (waiters & 1 != 0) return;
326 // someone else is currently waking up
327 if (waiters & WAKE != 0) return;
328
329 // try to decrease the waiter count & set the WAKE bit meaning a thread is waking up
330 if (@cmpxchgWeak(u32, &self.mutex.state.waiters, waiters, waiters - WAIT + WAKE, .Release, .Monotonic) == null) {
331 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
332 assert(rc == .SUCCESS);
333 return;
334 }
335 }
336 }
337 };
338};
339
340const TestContext = struct {
341 mutex: *Mutex,
342 data: i128,
343
344 const incr_count = 10000;
345};
346
347test "std.Mutex" {
348 var mutex = Mutex{};
349
350 var context = TestContext{
351 .mutex = &mutex,
352 .data = 0,
353 };
354
355 if (builtin.single_threaded) {
356 worker(&context);
357 testing.expect(context.data == TestContext.incr_count);
358 } else {
359 const thread_count = 10;
360 var threads: [thread_count]*std.Thread = undefined;
361 for (threads) |*t| {
362 t.* = try std.Thread.spawn(&context, worker);
363 }
364 for (threads) |t|
365 t.wait();
366
367 testing.expect(context.data == thread_count * TestContext.incr_count);
368 }
369}
370
371fn worker(ctx: *TestContext) void {
372 var i: usize = 0;
373 while (i != TestContext.incr_count) : (i += 1) {
374 const held = ctx.mutex.acquire();
375 defer held.release();
376
377 ctx.data += 1;
378 }
379}
lib/std/once.zig+1-1
...@@ -15,7 +15,7 @@ pub fn once(comptime f: fn () void) Once(f) {...@@ -15,7 +15,7 @@ pub fn once(comptime f: fn () void) Once(f) {
15pub fn Once(comptime f: fn () void) type {15pub fn Once(comptime f: fn () void) type {
16 return struct {16 return struct {
17 done: bool = false,17 done: bool = false,
18 mutex: std.Mutex = std.Mutex{},18 mutex: std.Thread.Mutex = std.Thread.Mutex{},
1919
20 /// Call the function `f`.20 /// Call the function `f`.
21 /// If `call` is invoked multiple times `f` will be executed only the21 /// If `call` is invoked multiple times `f` will be executed only the
lib/std/os/windows/bits.zig+5
...@@ -1635,3 +1635,8 @@ pub const OBJECT_NAME_INFORMATION = extern struct {...@@ -1635,3 +1635,8 @@ pub const OBJECT_NAME_INFORMATION = extern struct {
1635 Name: UNICODE_STRING,1635 Name: UNICODE_STRING,
1636};1636};
1637pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION;1637pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION;
1638
1639pub const SRWLOCK = usize;
1640pub const SRWLOCK_INIT: SRWLOCK = 0;
1641pub const CONDITION_VARIABLE = usize;
1642pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = 0;
lib/std/os/windows/kernel32.zig+13
...@@ -289,3 +289,16 @@ pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWO...@@ -289,3 +289,16 @@ pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWO
289pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(WINAPI) BOOL;289pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(WINAPI) BOOL;
290290
291pub extern "kernel32" fn FlushFileBuffers(hFile: HANDLE) callconv(WINAPI) BOOL;291pub extern "kernel32" fn FlushFileBuffers(hFile: HANDLE) callconv(WINAPI) BOOL;
292
293pub extern "kernel32" fn WakeAllConditionVariable(c: *CONDITION_VARIABLE) callconv(WINAPI) void;
294pub extern "kernel32" fn WakeConditionVariable(c: *CONDITION_VARIABLE) callconv(WINAPI) void;
295pub extern "kernel32" fn SleepConditionVariableSRW(
296 c: *CONDITION_VARIABLE,
297 s: *SRWLOCK,
298 t: DWORD,
299 f: ULONG,
300) callconv(WINAPI) BOOL;
301
302pub extern "kernel32" fn TryAcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) BOOL;
303pub extern "kernel32" fn AcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;
304pub extern "kernel32" fn ReleaseSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;
lib/std/std.zig+1-12
...@@ -13,7 +13,6 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;...@@ -13,7 +13,6 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
13pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;13pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
14pub const AutoHashMap = hash_map.AutoHashMap;14pub const AutoHashMap = hash_map.AutoHashMap;
15pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;15pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
16pub const AutoResetEvent = @import("auto_reset_event.zig").AutoResetEvent;
17pub const BufMap = @import("buf_map.zig").BufMap;16pub const BufMap = @import("buf_map.zig").BufMap;
18pub const BufSet = @import("buf_set.zig").BufSet;17pub const BufSet = @import("buf_set.zig").BufSet;
19pub const ChildProcess = @import("child_process.zig").ChildProcess;18pub const ChildProcess = @import("child_process.zig").ChildProcess;
...@@ -21,26 +20,21 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM...@@ -21,26 +20,21 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM
21pub const DynLib = @import("dynamic_library.zig").DynLib;20pub const DynLib = @import("dynamic_library.zig").DynLib;
22pub const HashMap = hash_map.HashMap;21pub const HashMap = hash_map.HashMap;
23pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;22pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;
24pub const mutex = @import("mutex.zig");
25pub const Mutex = mutex.Mutex;
26pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray;23pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray;
27pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;24pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;
28pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;25pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
29pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;26pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
30pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;27pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
31pub const Progress = @import("Progress.zig");28pub const Progress = @import("Progress.zig");
32pub const ResetEvent = @import("ResetEvent.zig");
33pub const SemanticVersion = @import("SemanticVersion.zig");29pub const SemanticVersion = @import("SemanticVersion.zig");
34pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;30pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
35pub const SpinLock = @import("SpinLock.zig");
36pub const StaticResetEvent = @import("StaticResetEvent.zig");
37pub const StringHashMap = hash_map.StringHashMap;31pub const StringHashMap = hash_map.StringHashMap;
38pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;32pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;
39pub const StringArrayHashMap = array_hash_map.StringArrayHashMap;33pub const StringArrayHashMap = array_hash_map.StringArrayHashMap;
40pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged;34pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged;
41pub const TailQueue = @import("linked_list.zig").TailQueue;35pub const TailQueue = @import("linked_list.zig").TailQueue;
42pub const Target = @import("target.zig").Target;36pub const Target = @import("target.zig").Target;
43pub const Thread = @import("thread.zig").Thread;37pub const Thread = @import("Thread.zig");
4438
45pub const array_hash_map = @import("array_hash_map.zig");39pub const array_hash_map = @import("array_hash_map.zig");
46pub const atomic = @import("atomic.zig");40pub const atomic = @import("atomic.zig");
...@@ -98,12 +92,7 @@ test "" {...@@ -98,12 +92,7 @@ test "" {
98 // server is hitting OOM. TODO revert this after stage2 arrives.92 // server is hitting OOM. TODO revert this after stage2 arrives.
99 _ = ChildProcess;93 _ = ChildProcess;
100 _ = DynLib;94 _ = DynLib;
101 _ = mutex;
102 _ = Mutex;
103 _ = Progress;95 _ = Progress;
104 _ = ResetEvent;
105 _ = SpinLock;
106 _ = StaticResetEvent;
107 _ = Target;96 _ = Target;
108 _ = Thread;97 _ = Thread;
10998
lib/std/thread.zig deleted-526
...@@ -1,526 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 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 = std.builtin;
8const os = std.os;
9const mem = std.mem;
10const windows = std.os.windows;
11const c = std.c;
12const assert = std.debug.assert;
13
14const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
15
16pub const Thread = struct {
17 data: Data,
18
19 pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc;
20
21 /// Represents a kernel thread handle.
22 /// May be an integer or a pointer depending on the platform.
23 /// On Linux and POSIX, this is the same as Id.
24 pub const Handle = if (use_pthreads)
25 c.pthread_t
26 else switch (std.Target.current.os.tag) {
27 .linux => i32,
28 .windows => windows.HANDLE,
29 else => void,
30 };
31
32 /// Represents a unique ID per thread.
33 /// May be an integer or pointer depending on the platform.
34 /// On Linux and POSIX, this is the same as Handle.
35 pub const Id = switch (std.Target.current.os.tag) {
36 .windows => windows.DWORD,
37 else => Handle,
38 };
39
40 pub const Data = if (use_pthreads)
41 struct {
42 handle: Thread.Handle,
43 memory: []u8,
44 }
45 else switch (std.Target.current.os.tag) {
46 .linux => struct {
47 handle: Thread.Handle,
48 memory: []align(mem.page_size) u8,
49 },
50 .windows => struct {
51 handle: Thread.Handle,
52 alloc_start: *c_void,
53 heap_handle: windows.HANDLE,
54 },
55 else => struct {},
56 };
57
58 /// Returns the ID of the calling thread.
59 /// Makes a syscall every time the function is called.
60 /// On Linux and POSIX, this Id is the same as a Handle.
61 pub fn getCurrentId() Id {
62 if (use_pthreads) {
63 return c.pthread_self();
64 } else
65 return switch (std.Target.current.os.tag) {
66 .linux => os.linux.gettid(),
67 .windows => windows.kernel32.GetCurrentThreadId(),
68 else => @compileError("Unsupported OS"),
69 };
70 }
71
72 /// Returns the handle of this thread.
73 /// On Linux and POSIX, this is the same as Id.
74 /// On Linux, it is possible that the thread spawned with `spawn`
75 /// finishes executing entirely before the clone syscall completes. In this
76 /// case, this function will return 0 rather than the no-longer-existing thread's
77 /// pid.
78 pub fn handle(self: Thread) Handle {
79 return self.data.handle;
80 }
81
82 pub fn wait(self: *Thread) void {
83 if (use_pthreads) {
84 const err = c.pthread_join(self.data.handle, null);
85 switch (err) {
86 0 => {},
87 os.EINVAL => unreachable,
88 os.ESRCH => unreachable,
89 os.EDEADLK => unreachable,
90 else => unreachable,
91 }
92 std.heap.c_allocator.free(self.data.memory);
93 std.heap.c_allocator.destroy(self);
94 } else switch (std.Target.current.os.tag) {
95 .linux => {
96 while (true) {
97 const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
98 if (pid_value == 0) break;
99 const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null);
100 switch (os.linux.getErrno(rc)) {
101 0 => continue,
102 os.EINTR => continue,
103 os.EAGAIN => continue,
104 else => unreachable,
105 }
106 }
107 os.munmap(self.data.memory);
108 },
109 .windows => {
110 windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable;
111 windows.CloseHandle(self.data.handle);
112 windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start);
113 },
114 else => @compileError("Unsupported OS"),
115 }
116 }
117
118 pub const SpawnError = error{
119 /// A system-imposed limit on the number of threads was encountered.
120 /// There are a number of limits that may trigger this error:
121 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
122 /// which limits the number of processes and threads for a real
123 /// user ID, was reached;
124 /// * the kernel's system-wide limit on the number of processes and
125 /// threads, /proc/sys/kernel/threads-max, was reached (see
126 /// proc(5));
127 /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was
128 /// reached (see proc(5)); or
129 /// * the PID limit (pids.max) imposed by the cgroup "process num‐
130 /// ber" (PIDs) controller was reached.
131 ThreadQuotaExceeded,
132
133 /// The kernel cannot allocate sufficient memory to allocate a task structure
134 /// for the child, or to copy those parts of the caller's context that need to
135 /// be copied.
136 SystemResources,
137
138 /// Not enough userland memory to spawn the thread.
139 OutOfMemory,
140
141 /// `mlockall` is enabled, and the memory needed to spawn the thread
142 /// would exceed the limit.
143 LockedMemoryLimitExceeded,
144
145 Unexpected,
146 };
147
148 /// caller must call wait on the returned thread
149 /// fn startFn(@TypeOf(context)) T
150 /// where T is u8, noreturn, void, or !void
151 /// caller must call wait on the returned thread
152 pub fn spawn(context: anytype, comptime startFn: anytype) SpawnError!*Thread {
153 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
154 // TODO compile-time call graph analysis to determine stack upper bound
155 // https://github.com/ziglang/zig/issues/157
156 const default_stack_size = 16 * 1024 * 1024;
157
158 const Context = @TypeOf(context);
159 comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context);
160
161 if (std.Target.current.os.tag == .windows) {
162 const WinThread = struct {
163 const OuterContext = struct {
164 thread: Thread,
165 inner: Context,
166 };
167 fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD {
168 const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
169
170 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
171 .NoReturn => {
172 startFn(arg);
173 },
174 .Void => {
175 startFn(arg);
176 return 0;
177 },
178 .Int => |info| {
179 if (info.bits != 8) {
180 @compileError(bad_startfn_ret);
181 }
182 return startFn(arg);
183 },
184 .ErrorUnion => |info| {
185 if (info.payload != void) {
186 @compileError(bad_startfn_ret);
187 }
188 startFn(arg) catch |err| {
189 std.debug.warn("error: {s}\n", .{@errorName(err)});
190 if (@errorReturnTrace()) |trace| {
191 std.debug.dumpStackTrace(trace.*);
192 }
193 };
194 return 0;
195 },
196 else => @compileError(bad_startfn_ret),
197 }
198 }
199 };
200
201 const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
202 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
203 const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;
204 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0);
205 const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];
206 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
207 outer_context.* = WinThread.OuterContext{
208 .thread = Thread{
209 .data = Thread.Data{
210 .heap_handle = heap_handle,
211 .alloc_start = bytes_ptr,
212 .handle = undefined,
213 },
214 },
215 .inner = context,
216 };
217
218 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);
219 outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {
220 switch (windows.kernel32.GetLastError()) {
221 else => |err| return windows.unexpectedError(err),
222 }
223 };
224 return &outer_context.thread;
225 }
226
227 const MainFuncs = struct {
228 fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 {
229 const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*;
230
231 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
232 .NoReturn => {
233 startFn(arg);
234 },
235 .Void => {
236 startFn(arg);
237 return 0;
238 },
239 .Int => |info| {
240 if (info.bits != 8) {
241 @compileError(bad_startfn_ret);
242 }
243 return startFn(arg);
244 },
245 .ErrorUnion => |info| {
246 if (info.payload != void) {
247 @compileError(bad_startfn_ret);
248 }
249 startFn(arg) catch |err| {
250 std.debug.warn("error: {s}\n", .{@errorName(err)});
251 if (@errorReturnTrace()) |trace| {
252 std.debug.dumpStackTrace(trace.*);
253 }
254 };
255 return 0;
256 },
257 else => @compileError(bad_startfn_ret),
258 }
259 }
260 fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void {
261 const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*;
262
263 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
264 .NoReturn => {
265 startFn(arg);
266 },
267 .Void => {
268 startFn(arg);
269 return null;
270 },
271 .Int => |info| {
272 if (info.bits != 8) {
273 @compileError(bad_startfn_ret);
274 }
275 // pthreads don't support exit status, ignore value
276 _ = startFn(arg);
277 return null;
278 },
279 .ErrorUnion => |info| {
280 if (info.payload != void) {
281 @compileError(bad_startfn_ret);
282 }
283 startFn(arg) catch |err| {
284 std.debug.warn("error: {s}\n", .{@errorName(err)});
285 if (@errorReturnTrace()) |trace| {
286 std.debug.dumpStackTrace(trace.*);
287 }
288 };
289 return null;
290 },
291 else => @compileError(bad_startfn_ret),
292 }
293 }
294 };
295
296 if (Thread.use_pthreads) {
297 var attr: c.pthread_attr_t = undefined;
298 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
299 defer assert(c.pthread_attr_destroy(&attr) == 0);
300
301 const thread_obj = try std.heap.c_allocator.create(Thread);
302 errdefer std.heap.c_allocator.destroy(thread_obj);
303 if (@sizeOf(Context) > 0) {
304 thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced(
305 u8,
306 @alignOf(Context),
307 @sizeOf(Context),
308 .at_least,
309 );
310 errdefer std.heap.c_allocator.free(thread_obj.data.memory);
311 mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context));
312 } else {
313 thread_obj.data.memory = @as([*]u8, undefined)[0..0];
314 }
315
316 // Use the same set of parameters used by the libc-less impl.
317 assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0);
318 assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0);
319
320 const err = c.pthread_create(
321 &thread_obj.data.handle,
322 &attr,
323 MainFuncs.posixThreadMain,
324 thread_obj.data.memory.ptr,
325 );
326 switch (err) {
327 0 => return thread_obj,
328 os.EAGAIN => return error.SystemResources,
329 os.EPERM => unreachable,
330 os.EINVAL => unreachable,
331 else => return os.unexpectedErrno(@intCast(usize, err)),
332 }
333
334 return thread_obj;
335 }
336
337 var guard_end_offset: usize = undefined;
338 var stack_end_offset: usize = undefined;
339 var thread_start_offset: usize = undefined;
340 var context_start_offset: usize = undefined;
341 var tls_start_offset: usize = undefined;
342 const mmap_len = blk: {
343 var l: usize = mem.page_size;
344 // Allocate a guard page right after the end of the stack region
345 guard_end_offset = l;
346 // The stack itself, which grows downwards.
347 l = mem.alignForward(l + default_stack_size, mem.page_size);
348 stack_end_offset = l;
349 // Above the stack, so that it can be in the same mmap call, put the Thread object.
350 l = mem.alignForward(l, @alignOf(Thread));
351 thread_start_offset = l;
352 l += @sizeOf(Thread);
353 // Next, the Context object.
354 if (@sizeOf(Context) != 0) {
355 l = mem.alignForward(l, @alignOf(Context));
356 context_start_offset = l;
357 l += @sizeOf(Context);
358 }
359 // Finally, the Thread Local Storage, if any.
360 l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align);
361 tls_start_offset = l;
362 l += os.linux.tls.tls_image.alloc_size;
363 // Round the size to the page size.
364 break :blk mem.alignForward(l, mem.page_size);
365 };
366
367 const mmap_slice = mem: {
368 // Map the whole stack with no rw permissions to avoid
369 // committing the whole region right away
370 const mmap_slice = os.mmap(
371 null,
372 mmap_len,
373 os.PROT_NONE,
374 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
375 -1,
376 0,
377 ) catch |err| switch (err) {
378 error.MemoryMappingNotSupported => unreachable,
379 error.AccessDenied => unreachable,
380 error.PermissionDenied => unreachable,
381 else => |e| return e,
382 };
383 errdefer os.munmap(mmap_slice);
384
385 // Map everything but the guard page as rw
386 os.mprotect(
387 mmap_slice[guard_end_offset..],
388 os.PROT_READ | os.PROT_WRITE,
389 ) catch |err| switch (err) {
390 error.AccessDenied => unreachable,
391 else => |e| return e,
392 };
393
394 break :mem mmap_slice;
395 };
396
397 const mmap_addr = @ptrToInt(mmap_slice.ptr);
398
399 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
400 thread_ptr.data.memory = mmap_slice;
401
402 var arg: usize = undefined;
403 if (@sizeOf(Context) != 0) {
404 arg = mmap_addr + context_start_offset;
405 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
406 context_ptr.* = context;
407 }
408
409 if (std.Target.current.os.tag == .linux) {
410 const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |
411 os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM |
412 os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
413 os.CLONE_DETACHED | os.CLONE_SETTLS;
414 // This structure is only needed when targeting i386
415 var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined;
416
417 const tls_area = mmap_slice[tls_start_offset..];
418 const tp_value = os.linux.tls.prepareTLS(tls_area);
419
420 const newtls = blk: {
421 if (std.Target.current.cpu.arch == .i386) {
422 user_desc = os.linux.user_desc{
423 .entry_number = os.linux.tls.tls_image.gdt_entry_number,
424 .base_addr = tp_value,
425 .limit = 0xfffff,
426 .seg_32bit = 1,
427 .contents = 0, // Data
428 .read_exec_only = 0,
429 .limit_in_pages = 1,
430 .seg_not_present = 0,
431 .useable = 1,
432 };
433 break :blk @ptrToInt(&user_desc);
434 } else {
435 break :blk tp_value;
436 }
437 };
438
439 const rc = os.linux.clone(
440 MainFuncs.linuxThreadMain,
441 mmap_addr + stack_end_offset,
442 flags,
443 arg,
444 &thread_ptr.data.handle,
445 newtls,
446 &thread_ptr.data.handle,
447 );
448 switch (os.errno(rc)) {
449 0 => return thread_ptr,
450 os.EAGAIN => return error.ThreadQuotaExceeded,
451 os.EINVAL => unreachable,
452 os.ENOMEM => return error.SystemResources,
453 os.ENOSPC => unreachable,
454 os.EPERM => unreachable,
455 os.EUSERS => unreachable,
456 else => |err| return os.unexpectedErrno(err),
457 }
458 } else {
459 @compileError("Unsupported OS");
460 }
461 }
462
463 pub const CpuCountError = error{
464 PermissionDenied,
465 SystemResources,
466 Unexpected,
467 };
468
469 pub fn cpuCount() CpuCountError!usize {
470 if (std.Target.current.os.tag == .linux) {
471 const cpu_set = try os.sched_getaffinity(0);
472 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
473 }
474 if (std.Target.current.os.tag == .windows) {
475 return os.windows.peb().NumberOfProcessors;
476 }
477 if (std.Target.current.os.tag == .openbsd) {
478 var count: c_int = undefined;
479 var count_size: usize = @sizeOf(c_int);
480 const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE };
481 os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
482 error.NameTooLong, error.UnknownName => unreachable,
483 else => |e| return e,
484 };
485 return @intCast(usize, count);
486 }
487 var count: c_int = undefined;
488 var count_len: usize = @sizeOf(c_int);
489 const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
490 os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
491 error.NameTooLong, error.UnknownName => unreachable,
492 else => |e| return e,
493 };
494 return @intCast(usize, count);
495 }
496
497 pub fn getCurrentThreadId() u64 {
498 switch (std.Target.current.os.tag) {
499 .linux => {
500 // Use the syscall directly as musl doesn't provide a wrapper.
501 return @bitCast(u32, os.linux.gettid());
502 },
503 .windows => {
504 return os.windows.kernel32.GetCurrentThreadId();
505 },
506 .macos, .ios, .watchos, .tvos => {
507 var thread_id: u64 = undefined;
508 // Pass thread=null to get the current thread ID.
509 assert(c.pthread_threadid_np(null, &thread_id) == 0);
510 return thread_id;
511 },
512 .netbsd => {
513 return @bitCast(u32, c._lwp_self());
514 },
515 .freebsd => {
516 return @bitCast(u32, c.pthread_getthreadid_np());
517 },
518 .openbsd => {
519 return @bitCast(u32, c.getthrid());
520 },
521 else => {
522 @compileError("getCurrentThreadId not implemented for this platform");
523 },
524 }
525 }
526};
src/Compilation.zig+1-1
...@@ -125,7 +125,7 @@ owned_link_dir: ?std.fs.Dir,...@@ -125,7 +125,7 @@ owned_link_dir: ?std.fs.Dir,
125color: @import("main.zig").Color = .auto,125color: @import("main.zig").Color = .auto,
126126
127/// This mutex guards all `Compilation` mutable state.127/// This mutex guards all `Compilation` mutable state.
128mutex: std.Mutex = .{},128mutex: std.Thread.Mutex = .{},
129129
130test_filter: ?[]const u8,130test_filter: ?[]const u8,
131test_name_prefix: ?[]const u8,131test_name_prefix: ?[]const u8,
src/ThreadPool.zig+2-2
...@@ -6,14 +6,14 @@...@@ -6,14 +6,14 @@
6const std = @import("std");6const std = @import("std");
7const ThreadPool = @This();7const ThreadPool = @This();
88
9lock: std.Mutex = .{},9lock: std.Thread.Mutex = .{},
10is_running: bool = true,10is_running: bool = true,
11allocator: *std.mem.Allocator,11allocator: *std.mem.Allocator,
12workers: []Worker,12workers: []Worker,
13run_queue: RunQueue = .{},13run_queue: RunQueue = .{},
14idle_queue: IdleQueue = .{},14idle_queue: IdleQueue = .{},
1515
16const IdleQueue = std.SinglyLinkedList(std.ResetEvent);16const IdleQueue = std.SinglyLinkedList(std.Thread.ResetEvent);
17const RunQueue = std.SinglyLinkedList(Runnable);17const RunQueue = std.SinglyLinkedList(Runnable);
18const Runnable = struct {18const Runnable = struct {
19 runFn: fn (*Runnable) void,19 runFn: fn (*Runnable) void,
src/WaitGroup.zig+2-2
...@@ -6,9 +6,9 @@...@@ -6,9 +6,9 @@
6const std = @import("std");6const std = @import("std");
7const WaitGroup = @This();7const WaitGroup = @This();
88
9lock: std.Mutex = .{},9lock: std.Thread.Mutex = .{},
10counter: usize = 0,10counter: usize = 0,
11event: std.ResetEvent,11event: std.Thread.ResetEvent,
1212
13pub fn init(self: *WaitGroup) !void {13pub fn init(self: *WaitGroup) !void {
14 self.* = .{14 self.* = .{