Skip to content

Commit a8e8af6

Browse files
committed
Make dependency tracking a compile time setting
1 parent 068303d commit a8e8af6

File tree

3 files changed

+77
-41
lines changed

3 files changed

+77
-41
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ name = "mutex"
3131
harness = false
3232

3333
[features]
34+
default = ["backtraces"]
35+
backtraces = []
3436
# Feature names do not match crate names pending namespaced features.
3537
lockapi = ["lock_api"]
3638
parkinglot = ["parking_lot", "lockapi"]

src/lib.rs

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,13 @@
4646
//!
4747
//! [paper]: https://whileydave.com/publications/pk07_jea/
4848
#![cfg_attr(docsrs, feature(doc_cfg))]
49-
use std::backtrace::Backtrace;
5049
use std::cell::RefCell;
5150
use std::fmt;
5251
use std::marker::PhantomData;
5352
use std::ops::Deref;
5453
use std::ops::DerefMut;
5554
use std::sync::atomic::AtomicUsize;
5655
use std::sync::atomic::Ordering;
57-
use std::sync::Arc;
5856
use std::sync::Mutex;
5957
use std::sync::MutexGuard;
6058
use std::sync::OnceLock;
@@ -66,6 +64,8 @@ pub use lock_api;
6664
#[cfg(feature = "parkinglot")]
6765
#[cfg_attr(docsrs, doc(cfg(feature = "parkinglot")))]
6866
pub use parking_lot;
67+
use reporting::Dep;
68+
use reporting::Reportable;
6969

7070
use crate::graph::DiGraph;
7171

@@ -76,6 +76,7 @@ pub mod lockapi;
7676
#[cfg(feature = "parkinglot")]
7777
#[cfg_attr(docsrs, doc(cfg(feature = "parkinglot")))]
7878
pub mod parkinglot;
79+
mod reporting;
7980
pub mod stdsync;
8081

8182
thread_local! {
@@ -143,24 +144,14 @@ impl MutexId {
143144
if let Some(&previous) = locks.borrow().last() {
144145
let mut graph = get_dependency_graph();
145146

146-
graph
147-
.add_edge(previous, self.value(), MutexDep::capture)
148-
.err()
147+
graph.add_edge(previous, self.value(), Dep::capture).err()
149148
} else {
150149
None
151150
}
152151
});
153152

154153
if let Some(cycle) = opt_cycle {
155-
// Panic without holding the lock to avoid needlessly poisoning it
156-
let mut message = String::from("Found cycle in mutex dependency graph:\n");
157-
158-
for entry in cycle {
159-
use std::fmt::Write;
160-
let _ = writeln!(message, "{entry}");
161-
}
162-
163-
panic!("{message}");
154+
panic!("{}", Dep::panic_message(&cycle))
164155
}
165156

166157
HELD_LOCKS.with(|locks| locks.borrow_mut().push(self.value()));
@@ -270,24 +261,9 @@ impl<'a> Drop for BorrowedMutex<'a> {
270261
}
271262
}
272263

273-
#[derive(Clone)]
274-
struct MutexDep(Arc<Backtrace>);
275-
276-
impl MutexDep {
277-
pub fn capture() -> Self {
278-
Self(Arc::new(Backtrace::capture()))
279-
}
280-
}
281-
282-
impl fmt::Display for MutexDep {
283-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284-
self.0.fmt(f)
285-
}
286-
}
287-
288264
/// Get a reference to the current dependency graph
289-
fn get_dependency_graph() -> impl DerefMut<Target = DiGraph<usize, MutexDep>> {
290-
static DEPENDENCY_GRAPH: OnceLock<Mutex<DiGraph<usize, MutexDep>>> = OnceLock::new();
265+
fn get_dependency_graph() -> impl DerefMut<Target = DiGraph<usize, Dep>> {
266+
static DEPENDENCY_GRAPH: OnceLock<Mutex<DiGraph<usize, Dep>>> = OnceLock::new();
291267

292268
DEPENDENCY_GRAPH
293269
.get_or_init(Default::default)
@@ -318,17 +294,11 @@ mod tests {
318294
let c = LazyMutexId::new();
319295

320296
let mut graph = get_dependency_graph();
321-
assert!(graph
322-
.add_edge(a.value(), b.value(), MutexDep::capture)
323-
.is_ok());
324-
assert!(graph
325-
.add_edge(b.value(), c.value(), MutexDep::capture)
326-
.is_ok());
297+
assert!(graph.add_edge(a.value(), b.value(), Dep::capture).is_ok());
298+
assert!(graph.add_edge(b.value(), c.value(), Dep::capture).is_ok());
327299

328300
// Creating an edge c → a should fail as it introduces a cycle.
329-
assert!(graph
330-
.add_edge(c.value(), a.value(), MutexDep::capture)
331-
.is_err());
301+
assert!(graph.add_edge(c.value(), a.value(), Dep::capture).is_err());
332302

333303
// Drop graph handle so we can drop vertices without deadlocking
334304
drop(graph);
@@ -337,7 +307,7 @@ mod tests {
337307

338308
// If b's destructor correctly ran correctly we can now add an edge from c to a.
339309
assert!(get_dependency_graph()
340-
.add_edge(c.value(), a.value(), MutexDep::capture)
310+
.add_edge(c.value(), a.value(), Dep::capture)
341311
.is_ok());
342312
}
343313

src/reporting.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! Cycle reporting primitives
2+
//!
3+
//! This module exposes [`Dep`], which resolves to either something that tracks dependencies or to
4+
//! something that doesn't. It should only be assumed to implement the [`Reportable`] trait.
5+
use std::backtrace::Backtrace;
6+
use std::borrow::Cow;
7+
use std::fmt::Write;
8+
use std::sync::Arc;
9+
10+
#[cfg(feature = "backtraces")]
11+
pub type Dep = MutexDep<Arc<Backtrace>>;
12+
#[cfg(not(feature = "backtraces"))]
13+
pub type Dep = MutexDep<()>;
14+
15+
// Base message to be reported when cycle is detected
16+
const BASE_MESSAGE: &str = "Found cycle in mutex dependency graph:";
17+
18+
pub trait Reportable: Clone {
19+
/// Capture the current state
20+
fn capture() -> Self;
21+
22+
/// Format a trace of state for human readable consumption.
23+
fn panic_message(trace: &[Self]) -> Cow<'static, str>;
24+
}
25+
26+
#[derive(Clone)]
27+
pub struct MutexDep<T>(T);
28+
29+
/// Use a unit as tracing data: no tracing.
30+
///
31+
/// This should have no runtime overhead for capturing traces and should therefore be cheap enough
32+
/// for most purposes.
33+
impl Reportable for MutexDep<()> {
34+
fn capture() -> Self {
35+
Self(())
36+
}
37+
38+
fn panic_message(_trace: &[Self]) -> Cow<'static, str> {
39+
Cow::Borrowed(BASE_MESSAGE)
40+
}
41+
}
42+
43+
/// Use a full backtrace as tracing data
44+
///
45+
/// Capture the entire backtrace which may be expensive. This implementation does not force capture
46+
/// in the event that backtraces are disabled at runtime, so the exact overhead can still be
47+
/// controlled a little.
48+
///
49+
/// N.B. the [`Backtrace`] needs to be wrapped in an Arc as backtraces are not [`Clone`].
50+
impl Reportable for MutexDep<Arc<Backtrace>> {
51+
fn capture() -> Self {
52+
Self(Arc::new(Backtrace::capture()))
53+
}
54+
55+
fn panic_message(trace: &[Self]) -> Cow<'static, str> {
56+
let mut message = format!("{BASE_MESSAGE}\n");
57+
58+
for entry in trace {
59+
let _ = writeln!(message, "{}", entry.0);
60+
}
61+
62+
message.into()
63+
}
64+
}

0 commit comments

Comments
 (0)