authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-03 20:06:50-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-03 20:46:25-08:00
logd45f9aca14bd36a710293b3d0092039fd91a571b
treeea084f371d318a53665e10f1d8b0decde5b7ce29
parentd1e01e94311ce4423f65d0d102d169ed59f1ca9d

std.Thread: delete Mutex.Recursive

Replaced by the lockStderr functions of std.Io. Trying to make `std.process.stderr_thread_mutex` be a bridge across different Io implementations didn't work in practice.

7 files changed, 39 insertions(+), 100 deletions(-)

lib/std/Io.zig+1-2
...@@ -2160,8 +2160,7 @@ pub const LockedStderr = struct {...@@ -2160,8 +2160,7 @@ pub const LockedStderr = struct {
21602160
2161/// For doing application-level writes to the standard error stream.2161/// For doing application-level writes to the standard error stream.
2162/// Coordinates also with debug-level writes that are ignorant of Io interface2162/// Coordinates also with debug-level writes that are ignorant of Io interface
2163/// and implementations. When this returns, `std.process.stderr_thread_mutex`2163/// and implementations.
2164/// will be locked.
2165///2164///
2166/// See also:2165/// See also:
2167/// * `tryLockStderr`2166/// * `tryLockStderr`
lib/std/Io/IoUring.zig+1-1
...@@ -10,7 +10,7 @@ const IoUring = std.os.linux.IoUring;...@@ -10,7 +10,7 @@ const IoUring = std.os.linux.IoUring;
1010
11/// Must be a thread-safe allocator.11/// Must be a thread-safe allocator.
12gpa: Allocator,12gpa: Allocator,
13mutex: std.Thread.Mutex,13mutex: Io.Mutex,
14main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)),14main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)),
15threads: Thread.List,15threads: Thread.List,
1616
lib/std/Io/Kqueue.zig+1-1
...@@ -15,7 +15,7 @@ const posix = std.posix;...@@ -15,7 +15,7 @@ const posix = std.posix;
1515
16/// Must be a thread-safe allocator.16/// Must be a thread-safe allocator.
17gpa: Allocator,17gpa: Allocator,
18mutex: std.Thread.Mutex,18mutex: Io.Mutex,
19main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)),19main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)),
20threads: Thread.List,20threads: Thread.List,
2121
lib/std/Io/Threaded.zig+34-5
...@@ -67,6 +67,9 @@ stderr_writer: File.Writer = .{...@@ -67,6 +67,9 @@ stderr_writer: File.Writer = .{
67},67},
68stderr_mode: Io.Terminal.Mode = .no_color,68stderr_mode: Io.Terminal.Mode = .no_color,
69stderr_writer_initialized: bool = false,69stderr_writer_initialized: bool = false,
70stderr_mutex: Io.Mutex = .init,
71stderr_mutex_locker: std.Thread.Id = Thread.invalid_id,
72stderr_mutex_lock_count: usize = 0,
7073
71argv0: Argv0,74argv0: Argv0,
72environ: Environ,75environ: Environ,
...@@ -689,6 +692,13 @@ const Thread = struct {...@@ -689,6 +692,13 @@ const Thread = struct {
689692
690 threadlocal var current: ?*Thread = null;693 threadlocal var current: ?*Thread = null;
691694
695 /// A value that does not alias any other thread id.
696 const invalid_id: std.Thread.Id = std.math.maxInt(std.Thread.Id);
697
698 fn currentId() std.Thread.Id {
699 return if (current) |t| t.id else std.Thread.getCurrentId();
700 }
701
692 /// The thread is neither in a syscall nor entering one, but we want to check for cancelation702 /// The thread is neither in a syscall nor entering one, but we want to check for cancelation
693 /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`.703 /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`.
694 fn checkCancel() Io.Cancelable!void {704 fn checkCancel() Io.Cancelable!void {
...@@ -13502,15 +13512,29 @@ fn netLookupFallible(...@@ -13502,15 +13512,29 @@ fn netLookupFallible(
1350213512
13503fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr {13513fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr {
13504 const t: *Threaded = @ptrCast(@alignCast(userdata));13514 const t: *Threaded = @ptrCast(@alignCast(userdata));
13505 // Only global mutex since this is Threaded.13515 const current_thread_id = Thread.currentId();
13506 process.stderr_thread_mutex.lock();13516
13517 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {
13518 mutexLock(&t.stderr_mutex);
13519 assert(t.stderr_mutex_lock_count == 0);
13520 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);
13521 }
13522 t.stderr_mutex_lock_count += 1;
13523
13507 return initLockedStderr(t, terminal_mode);13524 return initLockedStderr(t, terminal_mode);
13508}13525}
1350913526
13510fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr {13527fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr {
13511 const t: *Threaded = @ptrCast(@alignCast(userdata));13528 const t: *Threaded = @ptrCast(@alignCast(userdata));
13512 // Only global mutex since this is Threaded.13529 const current_thread_id = Thread.currentId();
13513 if (!process.stderr_thread_mutex.tryLock()) return null;13530
13531 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {
13532 if (!t.stderr_mutex.tryLock()) return null;
13533 assert(t.stderr_mutex_lock_count == 0);
13534 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);
13535 }
13536 t.stderr_mutex_lock_count += 1;
13537
13514 return try initLockedStderr(t, terminal_mode);13538 return try initLockedStderr(t, terminal_mode);
13515}13539}
1351613540
...@@ -13541,7 +13565,12 @@ fn unlockStderr(userdata: ?*anyopaque) void {...@@ -13541,7 +13565,12 @@ fn unlockStderr(userdata: ?*anyopaque) void {
13541 };13565 };
13542 t.stderr_writer.interface.end = 0;13566 t.stderr_writer.interface.end = 0;
13543 t.stderr_writer.interface.buffer = &.{};13567 t.stderr_writer.interface.buffer = &.{};
13544 process.stderr_thread_mutex.unlock();13568
13569 t.stderr_mutex_lock_count -= 1;
13570 if (t.stderr_mutex_lock_count == 0) {
13571 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, Thread.invalid_id, .unordered);
13572 mutexUnlock(&t.stderr_mutex);
13573 }
13545}13574}
1354613575
13547fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {13576fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {
lib/std/Thread.zig+2-12
...@@ -1,6 +1,5 @@...@@ -1,6 +1,5 @@
1//! This struct represents a kernel thread, and acts as a namespace for1//! This struct represents a kernel thread.
2//! concurrency primitives that operate on kernel threads. For concurrency2const Thread = @This();
3//! primitives that interact with the I/O interface, see `std.Io`.
43
5const builtin = @import("builtin");4const builtin = @import("builtin");
6const target = builtin.target;5const target = builtin.target;
...@@ -14,13 +13,8 @@ const posix = std.posix;...@@ -14,13 +13,8 @@ const posix = std.posix;
14const windows = std.os.windows;13const windows = std.os.windows;
15const testing = std.testing;14const testing = std.testing;
1615
17pub const Mutex = struct {
18 pub const Recursive = @import("Thread/Mutex/Recursive.zig");
19};
20
21pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;16pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;
2217
23const Thread = @This();
24const Impl = if (native_os == .windows)18const Impl = if (native_os == .windows)
25 WindowsThreadImpl19 WindowsThreadImpl
26else if (use_pthreads)20else if (use_pthreads)
...@@ -1604,10 +1598,6 @@ test "setName, getName" {...@@ -1604,10 +1598,6 @@ test "setName, getName" {
1604 thread.join();1598 thread.join();
1605}1599}
16061600
1607test {
1608 _ = Mutex;
1609}
1610
1611fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void {1601fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void {
1612 value.* += 1;1602 value.* += 1;
1613 event.set(io);1603 event.set(io);
lib/std/Thread/Mutex/Recursive.zig deleted-72
...@@ -1,72 +0,0 @@
1//! A synchronization primitive enforcing atomic access to a shared region of
2//! code known as the "critical section".
3//!
4//! Equivalent to `std.Mutex` except it allows the same thread to obtain the
5//! lock multiple times.
6//!
7//! A recursive mutex is an abstraction layer on top of a regular mutex;
8//! therefore it is recommended to use instead `std.Mutex` unless there is a
9//! specific reason a recursive mutex is warranted.
10const Recursive = @This();
11
12const std = @import("../../std.zig");
13const Io = std.Io;
14const assert = std.debug.assert;
15
16mutex: Io.Mutex,
17thread_id: std.Thread.Id,
18lock_count: usize,
19
20pub const init: Recursive = .{
21 .mutex = .init,
22 .thread_id = invalid_thread_id,
23 .lock_count = 0,
24};
25
26/// Acquires the `Mutex` without blocking the caller's thread.
27///
28/// Returns `false` if the calling thread would have to block to acquire it.
29///
30/// Otherwise, returns `true` and the caller should `unlock()` the Mutex to release it.
31pub fn tryLock(r: *Recursive) bool {
32 const current_thread_id = std.Thread.getCurrentId();
33 if (@atomicLoad(std.Thread.Id, &r.thread_id, .unordered) != current_thread_id) {
34 if (!r.mutex.tryLock()) return false;
35 assert(r.lock_count == 0);
36 @atomicStore(std.Thread.Id, &r.thread_id, current_thread_id, .unordered);
37 }
38 r.lock_count += 1;
39 return true;
40}
41
42/// Acquires the `Mutex`, blocking the current thread while the mutex is
43/// already held by another thread.
44///
45/// The `Mutex` can be held multiple times by the same thread.
46///
47/// Once acquired, call `unlock` on the `Mutex` to release it, regardless
48/// of whether the lock was already held by the same thread.
49pub fn lock(r: *Recursive) void {
50 const current_thread_id = std.Thread.getCurrentId();
51 if (@atomicLoad(std.Thread.Id, &r.thread_id, .unordered) != current_thread_id) {
52 Io.Threaded.mutexLock(&r.mutex);
53 assert(r.lock_count == 0);
54 @atomicStore(std.Thread.Id, &r.thread_id, current_thread_id, .unordered);
55 }
56 r.lock_count += 1;
57}
58
59/// Releases the `Mutex` which was previously acquired with `lock` or `tryLock`.
60///
61/// It is undefined behavior to unlock from a different thread that it was
62/// locked from.
63pub fn unlock(r: *Recursive) void {
64 r.lock_count -= 1;
65 if (r.lock_count == 0) {
66 @atomicStore(std.Thread.Id, &r.thread_id, invalid_thread_id, .unordered);
67 Io.Threaded.mutexUnlock(&r.mutex);
68 }
69}
70
71/// A value that does not alias any other thread id.
72const invalid_thread_id: std.Thread.Id = std.math.maxInt(std.Thread.Id);
lib/std/process.zig-7
...@@ -20,13 +20,6 @@ pub const Args = @import("process/Args.zig");...@@ -20,13 +20,6 @@ pub const Args = @import("process/Args.zig");
20pub const Environ = @import("process/Environ.zig");20pub const Environ = @import("process/Environ.zig");
21pub const Preopens = @import("process/Preopens.zig");21pub const Preopens = @import("process/Preopens.zig");
2222
23/// This is the global, process-wide protection to coordinate stderr writes.
24///
25/// The primary motivation for recursive mutex here is so that a panic while
26/// stderr mutex is held still dumps the stack trace and other debug
27/// information.
28pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
29
30/// A standard set of pre-initialized useful APIs for programs to take23/// A standard set of pre-initialized useful APIs for programs to take
31/// advantage of. This is the type of the first parameter of the main function.24/// advantage of. This is the type of the first parameter of the main function.
32/// Applications wanting more flexibility can accept `Init.Minimal` instead.25/// Applications wanting more flexibility can accept `Init.Minimal` instead.