blob: 0f6833412f723cdc53e6199f222ee29e057860f4 [file] [log] [blame]
//! Cycle reporting primitives
//!
//! This module exposes [`Dep`], which resolves to either something that tracks dependencies or to
//! something that doesn't. It should only be assumed to implement the [`Reportable`] trait.
use std::backtrace::Backtrace;
use std::borrow::Cow;
use std::fmt::Write;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature = "backtraces")]
pub type Dep = MutexDep<Arc<Backtrace>>;
#[cfg(not(feature = "backtraces"))]
pub type Dep = MutexDep<()>;
// Base message to be reported when cycle is detected
const BASE_MESSAGE: &str = "Found cycle in mutex dependency graph:";
static SHOULD_PANIC: AtomicBool = AtomicBool::new(true);
/// Call this early in main() to suppress panics when a cycle is detected and print the lock cycles
/// to stderr instead.
///
/// This is not as useful a mechanism for detecting issues as a panic, but it is useful for
/// incrementally rolling out usage of tracing-mutex to many programs that use a shared crate.
pub fn suppress_panics() {
SHOULD_PANIC.store(false, Ordering::Relaxed);
}
pub(crate) fn report_cycle(cycle: &[Dep]) {
let message = Dep::message(cycle);
if SHOULD_PANIC.load(Ordering::Relaxed) {
panic!("{message}");
} else {
eprintln!("{message}");
}
}
pub trait Reportable: Clone {
/// Capture the current state
fn capture() -> Self;
/// Format a trace of state for human readable consumption.
fn message(trace: &[Self]) -> Cow<'static, str>;
}
#[derive(Clone)]
pub struct MutexDep<T>(T);
/// Use a unit as tracing data: no tracing.
///
/// This should have no runtime overhead for capturing traces and should therefore be cheap enough
/// for most purposes.
impl Reportable for MutexDep<()> {
fn capture() -> Self {
Self(())
}
fn message(_trace: &[Self]) -> Cow<'static, str> {
Cow::Borrowed(BASE_MESSAGE)
}
}
/// Use a full backtrace as tracing data
///
/// Capture the entire backtrace which may be expensive. This implementation does not force capture
/// in the event that backtraces are disabled at runtime, so the exact overhead can still be
/// controlled a little.
///
/// N.B. the [`Backtrace`] needs to be wrapped in an Arc as backtraces are not [`Clone`].
impl Reportable for MutexDep<Arc<Backtrace>> {
fn capture() -> Self {
Self(Arc::new(Backtrace::capture()))
}
fn message(trace: &[Self]) -> Cow<'static, str> {
let mut message = format!("{BASE_MESSAGE}\n");
for entry in trace {
let _ = writeln!(message, "{}", entry.0);
}
message.into()
}
}