#![cfg_attr(docsrs, feature(doc_cfg))]
use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ops::DerefMut;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::sync::OnceLock;
use std::sync::PoisonError;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
#[cfg(feature = "lock_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "lockapi")))]
#[deprecated = "The top-level re-export `lock_api` is deprecated. Use `tracing_mutex::lockapi::raw` instead"]
pub use lock_api;
#[cfg(feature = "parking_lot")]
#[cfg_attr(docsrs, doc(cfg(feature = "parkinglot")))]
#[deprecated = "The top-level re-export `parking_lot` is deprecated. Use `tracing_mutex::parkinglot::raw` instead"]
pub use parking_lot;
use graph::DiGraph;
use reporting::Dep;
use reporting::Reportable;
mod graph;
#[cfg(any(feature = "lock_api", feature = "lockapi"))]
#[cfg_attr(docsrs, doc(cfg(feature = "lock_api")))]
#[cfg_attr(
all(not(docsrs), feature = "lockapi", not(feature = "lock_api")),
deprecated = "The `lockapi` feature has been renamed `lock_api`"
)]
pub mod lockapi;
#[cfg(any(feature = "parking_lot", feature = "parkinglot"))]
#[cfg_attr(docsrs, doc(cfg(feature = "parking_lot")))]
#[cfg_attr(
all(not(docsrs), feature = "parkinglot", not(feature = "parking_lot")),
deprecated = "The `parkinglot` feature has been renamed `parking_lot`"
)]
pub mod parkinglot;
mod reporting;
pub mod stdsync;
pub mod util;
thread_local! {
static HELD_LOCKS: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
}
struct MutexId(usize);
impl MutexId {
pub fn new() -> Self {
static ID_SEQUENCE: AtomicUsize = AtomicUsize::new(0);
ID_SEQUENCE
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |id| id.checked_add(1))
.map(Self)
.expect("Mutex ID wraparound happened, results unreliable")
}
pub fn value(&self) -> usize {
self.0
}
pub fn get_borrowed(&self) -> BorrowedMutex<'_> {
self.mark_held();
BorrowedMutex {
id: self,
_not_send: PhantomData,
}
}
pub fn mark_held(&self) {
let opt_cycle = HELD_LOCKS.with(|locks| {
if let Some(&previous) = locks.borrow().last() {
let mut graph = get_dependency_graph();
graph.add_edge(previous, self.value(), Dep::capture).err()
} else {
None
}
});
if let Some(cycle) = opt_cycle {
panic!("{}", Dep::panic_message(&cycle))
}
HELD_LOCKS.with(|locks| locks.borrow_mut().push(self.value()));
}
pub unsafe fn mark_released(&self) {
HELD_LOCKS.with(|locks| {
let mut locks = locks.borrow_mut();
for (i, &lock) in locks.iter().enumerate().rev() {
if lock == self.value() {
locks.remove(i);
return;
}
}
unreachable!("Tried to drop lock for mutex {:?} but it wasn't held", self)
});
}
pub fn with_held<T>(&self, f: impl FnOnce() -> T) -> T {
let _guard = self.get_borrowed();
f()
}
}
impl Default for MutexId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for MutexId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MutexID({:?})", self.0)
}
}
impl Drop for MutexId {
fn drop(&mut self) {
get_dependency_graph().remove_node(self.value());
}
}
struct LazyMutexId {
inner: OnceLock<MutexId>,
}
impl LazyMutexId {
pub const fn new() -> Self {
Self {
inner: OnceLock::new(),
}
}
}
impl fmt::Debug for LazyMutexId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.deref())
}
}
impl Default for LazyMutexId {
fn default() -> Self {
Self::new()
}
}
impl Deref for LazyMutexId {
type Target = MutexId;
fn deref(&self) -> &Self::Target {
self.inner.get_or_init(MutexId::new)
}
}
#[derive(Debug)]
struct BorrowedMutex<'a> {
id: &'a MutexId,
_not_send: PhantomData<MutexGuard<'static, ()>>,
}
impl Drop for BorrowedMutex<'_> {
fn drop(&mut self) {
unsafe { self.id.mark_released() };
}
}
fn get_dependency_graph() -> impl DerefMut<Target = DiGraph<usize, Dep>> {
static DEPENDENCY_GRAPH: OnceLock<Mutex<DiGraph<usize, Dep>>> = OnceLock::new();
DEPENDENCY_GRAPH
.get_or_init(Default::default)
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
#[cfg(test)]
mod tests {
use rand::seq::SliceRandom;
use rand::thread_rng;
use super::*;
#[test]
fn test_next_mutex_id() {
let initial = MutexId::new();
let next = MutexId::new();
assert!(initial.0 < next.0);
}
#[test]
fn test_lazy_mutex_id() {
let a = LazyMutexId::new();
let b = LazyMutexId::new();
let c = LazyMutexId::new();
let mut graph = get_dependency_graph();
assert!(graph.add_edge(a.value(), b.value(), Dep::capture).is_ok());
assert!(graph.add_edge(b.value(), c.value(), Dep::capture).is_ok());
assert!(graph.add_edge(c.value(), a.value(), Dep::capture).is_err());
drop(graph);
drop(b);
assert!(
get_dependency_graph()
.add_edge(c.value(), a.value(), Dep::capture)
.is_ok()
);
}
#[test]
#[should_panic]
fn test_mutex_id_conflict() {
let ids = [MutexId::new(), MutexId::new(), MutexId::new()];
for i in 0..3 {
let _first_lock = ids[i].get_borrowed();
let _second_lock = ids[(i + 1) % 3].get_borrowed();
}
}
#[test]
fn fuzz_mutex_id() {
const NUM_NODES: usize = 100;
let ids: Vec<MutexId> = (0..NUM_NODES).map(|_| Default::default()).collect();
let mut edges = Vec::with_capacity(NUM_NODES * NUM_NODES);
for i in 0..NUM_NODES {
for j in i..NUM_NODES {
if i != j {
edges.push((i, j));
}
}
}
edges.shuffle(&mut thread_rng());
for (x, y) in edges {
let _ignored = ids[x].get_borrowed();
let _ = ids[y].get_borrowed();
}
}
}