authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-06-13 00:21:12-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-06-13 00:21:12-04:00
log4aa15440c7a12bcc6bc0cd589ade02295549d48c
treeffbd491bd4ef97b9169dcfc89ba3c14b60cc03ef
parent0cef727e59d7b0c34756c09f64cbfe4490dcc3e7
parent5fc1f8a32bb7d66c0db04e497b89f7e33f408722
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20268 from ziglang/keep-calm-and-continue-panicking


4 files changed, 108 insertions(+), 28 deletions(-)

lib/std/Progress.zig+6-1
......@@ -521,6 +521,8 @@ fn windowsApiUpdateThreadRun() void {
521521/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
522522///
523523/// During the lock, any `std.Progress` information is cleared from the terminal.
524///
525/// The lock is recursive; the same thread may hold the lock multiple times.
524526pub fn lockStdErr() void {
525527 stderr_mutex.lock();
526528 clearWrittenWithEscapeCodes() catch {};
......@@ -1378,4 +1380,7 @@ const have_sigwinch = switch (builtin.os.tag) {
13781380 else => false,
13791381};
13801382
1381var stderr_mutex: std.Thread.Mutex = .{};
1383/// The primary motivation for recursive mutex here is so that a panic while
1384/// stderr mutex is held still dumps the stack trace and other debug
1385/// information.
1386var stderr_mutex = std.Thread.Mutex.Recursive.init;
lib/std/Thread/Mutex.zig+24-18
......@@ -1,23 +1,11 @@
1//! Mutex is a synchronization primitive which enforces atomic access to a shared region of code known as the "critical section".
2//! It does this by blocking ensuring only one thread is in the critical section at any given point in time by blocking the others.
3//! Mutex can be statically initialized and is at most `@sizeOf(u64)` large.
4//! Use `lock()` or `tryLock()` to enter the critical section and `unlock()` to leave it.
1//! Mutex is a synchronization primitive which enforces atomic access to a
2//! shared region of code known as the "critical section".
53//!
6//! Example:
7//! ```
8//! var m = Mutex{};
4//! It does this by blocking ensuring only one thread is in the critical
5//! section at any given point in time by blocking the others.
96//!
10//! {
11//! m.lock();
12//! defer m.unlock();
13//! // ... critical section code
14//! }
15//!
16//! if (m.tryLock()) {
17//! defer m.unlock();
18//! // ... critical section code
19//! }
20//! ```
7//! Mutex can be statically initialized and is at most `@sizeOf(u64)` large.
8//! Use `lock()` or `tryLock()` to enter the critical section and `unlock()` to leave it.
219
2210const std = @import("../std.zig");
2311const builtin = @import("builtin");
......@@ -30,6 +18,8 @@ const Futex = Thread.Futex;
3018
3119impl: Impl = .{},
3220
21pub const Recursive = @import("Mutex/Recursive.zig");
22
3323/// Tries to acquire the mutex without blocking the caller's thread.
3424/// Returns `false` if the calling thread would have to block to acquire it.
3525/// Otherwise, returns `true` and the caller should `unlock()` the Mutex to release it.
......@@ -312,3 +302,19 @@ test "many contended" {
312302
313303 try testing.expectEqual(runner.counter.get(), num_increments * num_threads);
314304}
305
306// https://github.com/ziglang/zig/issues/19295
307//test @This() {
308// var m: Mutex = .{};
309//
310// {
311// m.lock();
312// defer m.unlock();
313// // ... critical section code
314// }
315//
316// if (m.tryLock()) {
317// defer m.unlock();
318// // ... critical section code
319// }
320//}
lib/std/Thread/Mutex/Recursive.zig created+72
......@@ -0,0 +1,72 @@
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.
10
11const std = @import("../../std.zig");
12const Recursive = @This();
13const Mutex = std.Thread.Mutex;
14const assert = std.debug.assert;
15
16mutex: Mutex,
17thread_id: std.Thread.Id,
18lock_count: usize,
19
20pub const init: Recursive = .{
21 .mutex = .{},
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 r.mutex.lock();
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 r.mutex.unlock();
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/debug.zig+6-9
......@@ -447,9 +447,6 @@ pub fn panicExtra(
447447/// The counter is incremented/decremented atomically.
448448var panicking = std.atomic.Value(u8).init(0);
449449
450// Locked to avoid interleaving panic messages from multiple threads.
451var panic_mutex = std.Thread.Mutex{};
452
453450/// Counts how many times the panic handler is invoked by this thread.
454451/// This is used to catch and handle panics triggered by the panic handler.
455452threadlocal var panic_stage: usize = 0;
......@@ -474,8 +471,8 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
474471
475472 // Make sure to release the mutex when done
476473 {
477 panic_mutex.lock();
478 defer panic_mutex.unlock();
474 lockStdErr();
475 defer unlockStdErr();
479476
480477 const stderr = io.getStdErr().writer();
481478 if (builtin.single_threaded) {
......@@ -2604,8 +2601,8 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
26042601 _ = panicking.fetchAdd(1, .seq_cst);
26052602
26062603 {
2607 panic_mutex.lock();
2608 defer panic_mutex.unlock();
2604 lockStdErr();
2605 defer unlockStdErr();
26092606
26102607 dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr);
26112608 }
......@@ -2680,8 +2677,8 @@ fn handleSegfaultWindowsExtra(
26802677 _ = panicking.fetchAdd(1, .seq_cst);
26812678
26822679 {
2683 panic_mutex.lock();
2684 defer panic_mutex.unlock();
2680 lockStdErr();
2681 defer unlockStdErr();
26852682
26862683 dumpSegfaultInfoWindows(info, msg, label);
26872684 }