Skip to content

Commit 23cb14a

Browse files
committed
Allow suppressing panics.
This makes it possible to incrementally adopt tracing-mutex in a library shared by many binaries in the same project without having to fix all potential deadlocks in all binaries at once. The binaries that don't yet have proper lock hygeine can opt-out of real enforcement until their deadlocks are addressed.
1 parent 003f6a0 commit 23cb14a

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

examples/suppressed_panics.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! Show what a suppressed crash looks like.
2+
//!
3+
//! This shows what a traceback of a cycle detection looks like when panics are suppressed. It is
4+
//! expected to print the cycle when run in debug mode, because it might deadlock. In release mode,
5+
//! no tracing is used and the program may do any of the following:
6+
//!
7+
//! - Return a random valuation of `a`, `b`, and `c`. The implementation has a race-condition by
8+
//! design. I have observed (4, 3, 6), but also (6, 3, 5).
9+
//! - Deadlock forever.
10+
//!
11+
//! One can increase the SLEEP_TIME constant to increase the likelihood of a deadlock to occur. On
12+
//! my machine, 1ns of sleep time gives about a 50/50 chance of the program deadlocking.
13+
14+
use std::thread;
15+
use std::time::Duration;
16+
17+
use tracing_mutex::stdsync::Mutex;
18+
19+
fn main() {
20+
tracing_mutex::suppress_panics();
21+
22+
let a = Mutex::new(1);
23+
let b = Mutex::new(2);
24+
let c = Mutex::new(3);
25+
26+
// Increase this time to increase the likelihood of a deadlock.
27+
const SLEEP_TIME: Duration = Duration::from_nanos(1);
28+
29+
// Depending on random CPU performance, this section may deadlock, or may return a result. With
30+
// tracing enabled, the potential deadlock is always detected and a backtrace should be
31+
// produced.
32+
thread::scope(|s| {
33+
// Create an edge from a to b
34+
s.spawn(|| {
35+
let a = a.lock().unwrap();
36+
thread::sleep(SLEEP_TIME);
37+
*b.lock().unwrap() += *a;
38+
});
39+
40+
// Create an edge from b to c
41+
s.spawn(|| {
42+
let b = b.lock().unwrap();
43+
thread::sleep(SLEEP_TIME);
44+
*c.lock().unwrap() += *b;
45+
});
46+
47+
// Create an edge from c to a
48+
//
49+
// N.B. the program can deadlock on any of the three edges, as there is no guarantee which
50+
// thread will execute first. Nevertheless, any one of them is guaranteed to print with
51+
// tracing enabled.
52+
s.spawn(|| {
53+
let c = c.lock().unwrap();
54+
thread::sleep(SLEEP_TIME);
55+
*a.lock().unwrap() += *c;
56+
});
57+
});
58+
59+
println!(
60+
"{}, {}, {}",
61+
a.into_inner().unwrap(),
62+
b.into_inner().unwrap(),
63+
c.into_inner().unwrap()
64+
);
65+
}

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ mod reporting;
116116
pub mod stdsync;
117117
pub mod util;
118118

119+
pub use reporting::suppress_panics;
120+
119121
thread_local! {
120122
/// Stack to track which locks are held
121123
///
@@ -188,7 +190,7 @@ impl MutexId {
188190
});
189191

190192
if let Some(cycle) = opt_cycle {
191-
panic!("{}", Dep::panic_message(&cycle))
193+
reporting::report_cycle(&cycle);
192194
}
193195

194196
HELD_LOCKS.with(|locks| locks.borrow_mut().push(self.value()));

src/reporting.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::backtrace::Backtrace;
66
use std::borrow::Cow;
77
use std::fmt::Write;
88
use std::sync::Arc;
9+
use std::sync::atomic::{AtomicBool, Ordering};
910

1011
#[cfg(feature = "backtraces")]
1112
pub type Dep = MutexDep<Arc<Backtrace>>;
@@ -15,12 +16,32 @@ pub type Dep = MutexDep<()>;
1516
// Base message to be reported when cycle is detected
1617
const BASE_MESSAGE: &str = "Found cycle in mutex dependency graph:";
1718

19+
static SHOULD_PANIC: AtomicBool = AtomicBool::new(true);
20+
21+
/// Call this early in main() to suppress panics when a cycle is detected and print the lock cycles
22+
/// to stderr instead.
23+
///
24+
/// This is not as useful a mechanism for detecting issues as a panic, but it is useful for
25+
/// incrementally rolling out usage of tracing-mutex to many programs that use a shared crate.
26+
pub fn suppress_panics() {
27+
SHOULD_PANIC.store(false, Ordering::Relaxed);
28+
}
29+
30+
pub(crate) fn report_cycle(cycle: &[Dep]) {
31+
let message = Dep::message(cycle);
32+
if SHOULD_PANIC.load(Ordering::Relaxed) {
33+
panic!("{message}");
34+
} else {
35+
eprintln!("{message}");
36+
}
37+
}
38+
1839
pub trait Reportable: Clone {
1940
/// Capture the current state
2041
fn capture() -> Self;
2142

2243
/// Format a trace of state for human readable consumption.
23-
fn panic_message(trace: &[Self]) -> Cow<'static, str>;
44+
fn message(trace: &[Self]) -> Cow<'static, str>;
2445
}
2546

2647
#[derive(Clone)]
@@ -35,7 +56,7 @@ impl Reportable for MutexDep<()> {
3556
Self(())
3657
}
3758

38-
fn panic_message(_trace: &[Self]) -> Cow<'static, str> {
59+
fn message(_trace: &[Self]) -> Cow<'static, str> {
3960
Cow::Borrowed(BASE_MESSAGE)
4061
}
4162
}
@@ -52,7 +73,7 @@ impl Reportable for MutexDep<Arc<Backtrace>> {
5273
Self(Arc::new(Backtrace::capture()))
5374
}
5475

55-
fn panic_message(trace: &[Self]) -> Cow<'static, str> {
76+
fn message(trace: &[Self]) -> Cow<'static, str> {
5677
let mut message = format!("{BASE_MESSAGE}\n");
5778

5879
for entry in trace {

0 commit comments

Comments
 (0)