mirror of
https://github.com/bertptrs/tracing-mutex.git
synced 2025-12-27 21:40:32 +01:00
Document API and design
This commit is contained in:
28
src/graph.rs
28
src/graph.rs
@@ -7,17 +7,16 @@ type Order = usize;
|
|||||||
|
|
||||||
/// Directed Graph with dynamic topological sorting
|
/// Directed Graph with dynamic topological sorting
|
||||||
///
|
///
|
||||||
/// Design and implementation based "A Dynamic Topological Sort Algorithm for
|
/// Design and implementation based "A Dynamic Topological Sort Algorithm for Directed Acyclic
|
||||||
/// Directed Acyclic Graphs" by David J. Pearce and Paul H.J. Kelly which can
|
/// Graphs" by David J. Pearce and Paul H.J. Kelly which can be found on [the author's
|
||||||
/// be found on [the author's website][paper].
|
/// website][paper].
|
||||||
///
|
///
|
||||||
/// Variable- and method names have been chosen to reflect most closely
|
/// Variable- and method names have been chosen to reflect most closely resemble the names in the
|
||||||
/// resemble the names in the original paper.
|
/// original paper.
|
||||||
///
|
///
|
||||||
/// This digraph tracks its own topological order and updates it when new edges
|
/// This digraph tracks its own topological order and updates it when new edges are added to the
|
||||||
/// are added to the graph. After a cycle has been introduced, the order is no
|
/// graph. After a cycle has been introduced, the order is no longer kept up to date as it doesn't
|
||||||
/// longer kept up to date as it doesn't exist, but new edges are still
|
/// exist, but new edges are still tracked. Nodes are added implicitly when they're used in edges.
|
||||||
/// tracked. Nodes are added implicitly when they're used in edges.
|
|
||||||
///
|
///
|
||||||
/// [paper]: https://whileydave.com/publications/pk07_jea/
|
/// [paper]: https://whileydave.com/publications/pk07_jea/
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
@@ -36,10 +35,9 @@ pub struct DiGraph {
|
|||||||
impl DiGraph {
|
impl DiGraph {
|
||||||
/// Add a new node to the graph.
|
/// Add a new node to the graph.
|
||||||
///
|
///
|
||||||
/// If the node already existed, this function does not add it and uses the
|
/// If the node already existed, this function does not add it and uses the existing node data.
|
||||||
/// existing node data. The function returns mutable references to the
|
/// The function returns mutable references to the in-edges, out-edges, and finally the index of
|
||||||
/// in-edges, out-edges, and finally the index of the node in the topological
|
/// the node in the topological order.
|
||||||
/// order.
|
|
||||||
///
|
///
|
||||||
/// New nodes are appended to the end of the topological order when added.
|
/// New nodes are appended to the end of the topological order when added.
|
||||||
fn add_node(&mut self, n: MutexId) -> (&mut HashSet<MutexId>, &mut HashSet<MutexId>, Order) {
|
fn add_node(&mut self, n: MutexId) -> (&mut HashSet<MutexId>, &mut HashSet<MutexId>, Order) {
|
||||||
@@ -183,8 +181,8 @@ impl DiGraph {
|
|||||||
l.push(v);
|
l.push(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Original paper cleverly merges the two lists by using that both are
|
// Original paper cleverly merges the two lists by using that both are sorted. We just sort
|
||||||
// sorted. We just sort again. This is slower but also much simpler.
|
// again. This is slower but also much simpler.
|
||||||
orders.sort_unstable();
|
orders.sort_unstable();
|
||||||
|
|
||||||
for (node, order) in l.into_iter().zip(orders) {
|
for (node, order) in l.into_iter().zip(orders) {
|
||||||
|
|||||||
47
src/lib.rs
47
src/lib.rs
@@ -1,3 +1,50 @@
|
|||||||
|
//! Mutexes can deadlock each other, but you can avoid this by always acquiring your locks in a
|
||||||
|
//! consistent order. This crate provides tracing to ensure that you do.
|
||||||
|
//!
|
||||||
|
//! This crate tracks a virtual "stack" of locks that the current thread holds, and whenever a new
|
||||||
|
//! lock is acquired, a dependency is created from the last lock to the new one. These dependencies
|
||||||
|
//! together form a graph. As long as that graph does not contain any cycles, your program is
|
||||||
|
//! guaranteed to never deadlock.
|
||||||
|
//!
|
||||||
|
//! # Panics
|
||||||
|
//!
|
||||||
|
//! The primary method by which this crate signals an invalid lock acquisition order is by
|
||||||
|
//! panicking. When a cycle is created in the dependency graph when acquiring a lock, the thread
|
||||||
|
//! will instead panic. This panic will not poison the underlying mutex. Each following acquired
|
||||||
|
//! that introduces a **new** dependency will also panic, until enough mutexes are deallocated to
|
||||||
|
//! break the cycle in the graph.
|
||||||
|
//!
|
||||||
|
//! # Structure
|
||||||
|
//!
|
||||||
|
//! Each module in this crate exposes wrappers for a specific base-mutex with dependency trakcing
|
||||||
|
//! added. For now, that is limited to [`stdsync`] which provides wrappers for the base locks in the
|
||||||
|
//! standard library. More back-ends may be added as features in the future.
|
||||||
|
//!
|
||||||
|
//! # Performance considerations
|
||||||
|
//!
|
||||||
|
//! Tracing a mutex adds overhead to certain mutex operations in order to do the required
|
||||||
|
//! bookkeeping. The following actions have the following overhead.
|
||||||
|
//!
|
||||||
|
//! - **Acquiring a lock** locks the global dependency graph temporarily to check if the new lock
|
||||||
|
//! would introduce a cyclic dependency. This crate uses the algorithm proposed in ["A Dynamic
|
||||||
|
//! Topological Sort Algorithm for Directed Acyclic Graphs" by David J. Pearce and Paul H.J.
|
||||||
|
//! Kelly][paper] to detect cycles as efficently as possible. In addition, a thread local lock set
|
||||||
|
//! is updated with the new lock.
|
||||||
|
//!
|
||||||
|
//! - **Releasing a lock** updates a thread local lock set to remove the released lock.
|
||||||
|
//!
|
||||||
|
//! - **Allocating a lock** performs an atomic update to a shared counter.
|
||||||
|
//!
|
||||||
|
//! - **Deallocating a mutex** temporarily locks the global dependency graph to remove the lock from
|
||||||
|
//! it. If the graph contained a cycle, a complete scan of the (now pruned) graph is done to
|
||||||
|
//! determine if this is still the case.
|
||||||
|
//!
|
||||||
|
//! These operations have been reasonably optimized, but the performance penalty may yet be too much
|
||||||
|
//! for production use. In those cases, it may be beneficial to instead use debug-only versions
|
||||||
|
//! (such as [`stdsync::DebugMutex`]) which evaluate to a tracing mutex when debug assertions are
|
||||||
|
//! enabled, and to the underlying mutex when they're not.
|
||||||
|
//!
|
||||||
|
//! [paper]: https://whileydave.com/publications/pk07_jea/
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! Tracing mutex wrappers for types found in `std::sync`.
|
//! Tracing mutex wrappers for locks found in `std::sync`.
|
||||||
//!
|
//!
|
||||||
//! This module provides wrappers for `std::sync` primitives with exactly the same API and
|
//! This module provides wrappers for `std::sync` primitives with exactly the same API and
|
||||||
//! functionality as their counterparts, with the exception that their acquisition order is
|
//! functionality as their counterparts, with the exception that their acquisition order is
|
||||||
@@ -32,14 +32,40 @@ use crate::get_depedency_graph;
|
|||||||
use crate::BorrowedMutex;
|
use crate::BorrowedMutex;
|
||||||
use crate::MutexId;
|
use crate::MutexId;
|
||||||
|
|
||||||
/// Wrapper for `std::sync::Mutex`
|
/// Debug-only tracing `Mutex`.
|
||||||
|
///
|
||||||
|
/// Type alias that resolves to [`TracingMutex`] when debug assertions are enabled and to
|
||||||
|
/// [`std::sync::Mutex`] when they're not. Use this if you want to have the benefits of cycle
|
||||||
|
/// detection in development but do not want to pay the performance penalty in release.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub type DebugMutex<T> = TracingMutex<T>;
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
pub type DebugMutex<T> = Mutex<T>;
|
||||||
|
|
||||||
|
/// Debug-only tracing `RwLock`.
|
||||||
|
///
|
||||||
|
/// Type alias that resolves to [`RwLock`] when debug assertions are enabled and to
|
||||||
|
/// [`std::sync::RwLock`] when they're not. Use this if you want to have the benefits of cycle
|
||||||
|
/// detection in development but do not want to pay the performance penalty in release.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub type DebugRwLock<T> = TracingRwLock<T>;
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
pub type DebugRwLock<T> = RwLock<T>;
|
||||||
|
|
||||||
|
/// Wrapper for [`std::sync::Mutex`].
|
||||||
|
///
|
||||||
|
/// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and
|
||||||
|
/// the one it wraps.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TracingMutex<T> {
|
pub struct TracingMutex<T> {
|
||||||
inner: Mutex<T>,
|
inner: Mutex<T>,
|
||||||
id: MutexId,
|
id: MutexId,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for `std::sync::MutexGuard`
|
/// Wrapper for [`std::sync::MutexGuard`].
|
||||||
|
///
|
||||||
|
/// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and
|
||||||
|
/// the one it wraps.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TracingMutexGuard<'a, T> {
|
pub struct TracingMutexGuard<'a, T> {
|
||||||
inner: MutexGuard<'a, T>,
|
inner: MutexGuard<'a, T>,
|
||||||
@@ -164,14 +190,14 @@ impl<'a, T: fmt::Display> fmt::Display for TracingMutexGuard<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for `std::sync::RwLock`
|
/// Wrapper for [`std::sync::RwLock`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TracingRwLock<T> {
|
pub struct TracingRwLock<T> {
|
||||||
inner: RwLock<T>,
|
inner: RwLock<T>,
|
||||||
id: MutexId,
|
id: MutexId,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hybrid wrapper for both `std::sync::RwLockReadGuard` and `std::sync::RwLockWriteGuard`.
|
/// Hybrid wrapper for both [`std::sync::RwLockReadGuard`] and [`std::sync::RwLockWriteGuard`].
|
||||||
///
|
///
|
||||||
/// Please refer to [`TracingReadGuard`] and [`TracingWriteGuard`] for usable types.
|
/// Please refer to [`TracingReadGuard`] and [`TracingWriteGuard`] for usable types.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -180,9 +206,9 @@ pub struct TracingRwLockGuard<L> {
|
|||||||
mutex: BorrowedMutex,
|
mutex: BorrowedMutex,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper around `std::sync::RwLockReadGuard`.
|
/// Wrapper around [`std::sync::RwLockReadGuard`].
|
||||||
pub type TracingReadGuard<'a, T> = TracingRwLockGuard<RwLockReadGuard<'a, T>>;
|
pub type TracingReadGuard<'a, T> = TracingRwLockGuard<RwLockReadGuard<'a, T>>;
|
||||||
/// Wrapper around `std::sync::RwLockWriteGuard`.
|
/// Wrapper around [`std::sync::RwLockWriteGuard`].
|
||||||
pub type TracingWriteGuard<'a, T> = TracingRwLockGuard<RwLockWriteGuard<'a, T>>;
|
pub type TracingWriteGuard<'a, T> = TracingRwLockGuard<RwLockWriteGuard<'a, T>>;
|
||||||
|
|
||||||
impl<T> TracingRwLock<T> {
|
impl<T> TracingRwLock<T> {
|
||||||
|
|||||||
Reference in New Issue
Block a user