From 6e5516eaa70049fd1078f1de1c5dbce2ee29fc43 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Mon, 4 Jul 2022 21:25:46 +0200 Subject: [PATCH] Restructure std::sync wrappers --- src/lib.rs | 2 +- src/stdsync.rs | 1101 +++++++++++++++++++++++------------------------- 2 files changed, 526 insertions(+), 577 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6d5bef8..39a1efc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ //! //! 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 +//! (such as [`stdsync::Mutex`]) 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/ diff --git a/src/stdsync.rs b/src/stdsync.rs index 3cdb4d3..c6e95f7 100644 --- a/src/stdsync.rs +++ b/src/stdsync.rs @@ -1,625 +1,574 @@ //! Tracing mutex wrappers for locks found in `std::sync`. //! //! 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 -//! tracked. +//! functionality as their counterparts, with the exception that their acquisition order is tracked. +//! +//! Dedicated wrappers that provide the dependency tracing can be found in the [`tracing`] module. +//! The original primitives are available from [`std::sync`], imported as [`raw`] for convenience. +//! +//! If debug assertions are enabled, this module imports the primitives from [`tracing`], otherwise +//! it will import from [`raw`]. //! //! ```rust -//! # use tracing_mutex::stdsync::TracingMutex; -//! # use tracing_mutex::stdsync::TracingRwLock; -//! let mutex = TracingMutex::new(()); +//! # use tracing_mutex::stdsync::tracing::Mutex; +//! # use tracing_mutex::stdsync::tracing::RwLock; +//! let mutex = Mutex::new(()); //! mutex.lock().unwrap(); //! -//! let rwlock = TracingRwLock::new(()); +//! let rwlock = RwLock::new(()); //! rwlock.read().unwrap(); //! ``` -use std::fmt; -use std::ops::Deref; -use std::ops::DerefMut; -use std::sync::Condvar; -use std::sync::LockResult; -use std::sync::Mutex; -use std::sync::MutexGuard; -use std::sync::Once; -use std::sync::OnceState; -use std::sync::PoisonError; -use std::sync::RwLock; -use std::sync::RwLockReadGuard; -use std::sync::RwLockWriteGuard; -use std::sync::TryLockError; -use std::sync::TryLockResult; -use std::sync::WaitTimeoutResult; -use std::time::Duration; +pub use std::sync as raw; -use crate::BorrowedMutex; -use crate::LazyMutexId; -use crate::MutexId; - -/// 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 = TracingMutex; #[cfg(not(debug_assertions))] -pub type DebugMutex = Mutex; +pub use std::sync::{Condvar, Mutex, MutexGuard, Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; -/// Mutex guard for [`DebugMutex`]. #[cfg(debug_assertions)] -pub type DebugMutexGuard<'a, T> = TracingMutexGuard<'a, T>; -#[cfg(not(debug_assertions))] -pub type DebugMutexGuard<'a, T> = MutexGuard<'a, T>; +pub use tracing::{Condvar, Mutex, MutexGuard, Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; -/// Debug-only `Condvar` -/// -/// Type alias that accepts the mutex guard emitted from [`DebugMutex`]. -#[cfg(debug_assertions)] -pub type DebugCondvar = TracingCondvar; -#[cfg(not(debug_assertions))] -pub type DebugCondvar = Condvar; +/// Dependency tracing versions of [`std::sync`]. +pub mod tracing { + use std::fmt; + use std::ops::Deref; + use std::ops::DerefMut; + use std::sync; + use std::sync::LockResult; + use std::sync::OnceState; + use std::sync::PoisonError; + use std::sync::TryLockError; + use std::sync::TryLockResult; + use std::sync::WaitTimeoutResult; + use std::time::Duration; -/// Debug-only tracing `RwLock`. -/// -/// Type alias that resolves to [`TracingRwLock`] 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 = TracingRwLock; -#[cfg(not(debug_assertions))] -pub type DebugRwLock = RwLock; + use crate::BorrowedMutex; + use crate::LazyMutexId; + use crate::MutexId; -/// Read guard for [`DebugRwLock`]. -#[cfg(debug_assertions)] -pub type DebugReadGuard<'a, T> = TracingReadGuard<'a, T>; -#[cfg(not(debug_assertions))] -pub type DebugReadGuard<'a, T> = RwLockReadGuard<'a, T>; - -/// Write guard for [`DebugRwLock`]. -#[cfg(debug_assertions)] -pub type DebugWriteGuard<'a, T> = TracingWriteGuard<'a, T>; -#[cfg(not(debug_assertions))] -pub type DebugWriteGuard<'a, T> = RwLockWriteGuard<'a, T>; - -/// Debug-only tracing `Once`. -/// -/// Type alias that resolves to [`TracingOnce`] when debug assertions are enabled and to -/// [`std::sync::Once`] 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 DebugOnce = TracingOnce; -#[cfg(not(debug_assertions))] -pub type DebugOnce = Once; - -/// 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, Default)] -pub struct TracingMutex { - inner: Mutex, - id: MutexId, -} - -/// 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)] -pub struct TracingMutexGuard<'a, T> { - inner: MutexGuard<'a, T>, - _mutex: BorrowedMutex<'a>, -} - -fn map_lockresult(result: LockResult, mapper: F) -> LockResult -where - F: FnOnce(I) -> T, -{ - match result { - Ok(inner) => Ok(mapper(inner)), - Err(poisoned) => Err(PoisonError::new(mapper(poisoned.into_inner()))), + /// Wrapper for [`std::sync::Mutex`]. + /// + /// Refer to the [crate-level][`crate`] documentation for the differences between this struct and + /// the one it wraps. + #[derive(Debug, Default)] + pub struct Mutex { + inner: sync::Mutex, + id: MutexId, } -} -fn map_trylockresult(result: TryLockResult, mapper: F) -> TryLockResult -where - F: FnOnce(I) -> T, -{ - match result { - Ok(inner) => Ok(mapper(inner)), - Err(TryLockError::WouldBlock) => Err(TryLockError::WouldBlock), - Err(TryLockError::Poisoned(poisoned)) => { - Err(PoisonError::new(mapper(poisoned.into_inner())).into()) - } + /// Wrapper for [`std::sync::MutexGuard`]. + /// + /// Refer to the [crate-level][`crate`] documentation for the differences between this struct and + /// the one it wraps. + #[derive(Debug)] + pub struct MutexGuard<'a, T> { + inner: sync::MutexGuard<'a, T>, + _mutex: BorrowedMutex<'a>, } -} -impl TracingMutex { - /// Create a new tracing mutex with the provided value. - pub fn new(t: T) -> Self { - Self { - inner: Mutex::new(t), - id: MutexId::new(), + fn map_lockresult(result: LockResult, mapper: F) -> LockResult + where + F: FnOnce(I) -> T, + { + match result { + Ok(inner) => Ok(mapper(inner)), + Err(poisoned) => Err(PoisonError::new(mapper(poisoned.into_inner()))), } } - /// Wrapper for [`std::sync::Mutex::lock`]. - /// - /// # Panics - /// - /// This method participates in lock dependency tracking. If acquiring this lock introduces a - /// dependency cycle, this method will panic. - #[track_caller] - pub fn lock(&self) -> LockResult> { - let mutex = self.id.get_borrowed(); - let result = self.inner.lock(); - - let mapper = |guard| TracingMutexGuard { - _mutex: mutex, - inner: guard, - }; - - map_lockresult(result, mapper) - } - - /// Wrapper for [`std::sync::Mutex::try_lock`]. - /// - /// # Panics - /// - /// This method participates in lock dependency tracking. If acquiring this lock introduces a - /// dependency cycle, this method will panic. - #[track_caller] - pub fn try_lock(&self) -> TryLockResult> { - let mutex = self.id.get_borrowed(); - let result = self.inner.try_lock(); - - let mapper = |guard| TracingMutexGuard { - _mutex: mutex, - inner: guard, - }; - - map_trylockresult(result, mapper) - } - - /// Wrapper for [`std::sync::Mutex::is_poisoned`]. - pub fn is_poisoned(&self) -> bool { - self.inner.is_poisoned() - } - - /// Return a mutable reference to the underlying data. - /// - /// This method does not block as the locking is handled compile-time by the type system. - pub fn get_mut(&mut self) -> LockResult<&mut T> { - self.inner.get_mut() - } - - /// Unwrap the mutex and return its inner value. - pub fn into_inner(self) -> LockResult { - self.inner.into_inner() - } -} - -impl From for TracingMutex { - fn from(t: T) -> Self { - Self::new(t) - } -} - -impl<'a, T> Deref for TracingMutexGuard<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl<'a, T> DerefMut for TracingMutexGuard<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -impl<'a, T: fmt::Display> fmt::Display for TracingMutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -/// Wrapper around [`std::sync::Condvar`]. -/// -/// Allows `TracingMutexGuard` to be used with a `Condvar`. Unlike other structs in this module, -/// this wrapper does not add any additional dependency tracking or other overhead on top of the -/// primitive it wraps. All dependency tracking happens through the mutexes itself. -/// -/// # Panics -/// -/// This struct does not add any panics over the base implementation of `Condvar`, but panics due to -/// dependency tracking may poison associated mutexes. -/// -/// # Examples -/// -/// ``` -/// use std::sync::Arc; -/// use std::thread; -/// -/// use tracing_mutex::stdsync::{TracingCondvar, TracingMutex}; -/// -/// let pair = Arc::new((TracingMutex::new(false), TracingCondvar::new())); -/// let pair2 = Arc::clone(&pair); -/// -/// // Spawn a thread that will unlock the condvar -/// thread::spawn(move || { -/// let (lock, condvar) = &*pair2; -/// *lock.lock().unwrap() = true; -/// condvar.notify_one(); -/// }); -/// -/// // Wait until the thread unlocks the condvar -/// let (lock, condvar) = &*pair; -/// let guard = lock.lock().unwrap(); -/// let guard = condvar.wait_while(guard, |started| !*started).unwrap(); -/// -/// // Guard should read true now -/// assert!(*guard); -/// ``` -#[derive(Debug, Default)] -pub struct TracingCondvar(Condvar); - -impl TracingCondvar { - /// Creates a new condition variable which is ready to be waited on and notified. - pub fn new() -> Self { - Default::default() - } - - /// Wrapper for [`std::sync::Condvar::wait`]. - pub fn wait<'a, T>( - &self, - guard: TracingMutexGuard<'a, T>, - ) -> LockResult> { - let TracingMutexGuard { _mutex, inner } = guard; - - map_lockresult(self.0.wait(inner), |inner| TracingMutexGuard { - _mutex, - inner, - }) - } - - /// Wrapper for [`std::sync::Condvar::wait_while`]. - pub fn wait_while<'a, T, F>( - &self, - guard: TracingMutexGuard<'a, T>, - condition: F, - ) -> LockResult> + fn map_trylockresult(result: TryLockResult, mapper: F) -> TryLockResult where - F: FnMut(&mut T) -> bool, + F: FnOnce(I) -> T, { - let TracingMutexGuard { _mutex, inner } = guard; - - map_lockresult(self.0.wait_while(inner, condition), |inner| { - TracingMutexGuard { _mutex, inner } - }) - } - - /// Wrapper for [`std::sync::Condvar::wait_timeout`]. - pub fn wait_timeout<'a, T>( - &self, - guard: TracingMutexGuard<'a, T>, - dur: Duration, - ) -> LockResult<(TracingMutexGuard<'a, T>, WaitTimeoutResult)> { - let TracingMutexGuard { _mutex, inner } = guard; - - map_lockresult(self.0.wait_timeout(inner, dur), |(inner, result)| { - (TracingMutexGuard { _mutex, inner }, result) - }) - } - - /// Wrapper for [`std::sync::Condvar::wait_timeout_while`]. - pub fn wait_timeout_while<'a, T, F>( - &self, - guard: TracingMutexGuard<'a, T>, - dur: Duration, - condition: F, - ) -> LockResult<(TracingMutexGuard<'a, T>, WaitTimeoutResult)> - where - F: FnMut(&mut T) -> bool, - { - let TracingMutexGuard { _mutex, inner } = guard; - - map_lockresult( - self.0.wait_timeout_while(inner, dur, condition), - |(inner, result)| (TracingMutexGuard { _mutex, inner }, result), - ) - } - - /// Wrapper for [`std::sync::Condvar::notify_one`]. - pub fn notify_one(&self) { - self.0.notify_one(); - } - - /// Wrapper for [`std::sync::Condvar::notify_all`]. - pub fn notify_all(&self) { - self.0.notify_all(); - } -} - -/// Wrapper for [`std::sync::RwLock`]. -#[derive(Debug, Default)] -pub struct TracingRwLock { - inner: RwLock, - id: MutexId, -} - -/// Hybrid wrapper for both [`std::sync::RwLockReadGuard`] and [`std::sync::RwLockWriteGuard`]. -/// -/// Please refer to [`TracingReadGuard`] and [`TracingWriteGuard`] for usable types. -#[derive(Debug)] -pub struct TracingRwLockGuard<'a, L> { - inner: L, - _mutex: BorrowedMutex<'a>, -} - -/// Wrapper around [`std::sync::RwLockReadGuard`]. -pub type TracingReadGuard<'a, T> = TracingRwLockGuard<'a, RwLockReadGuard<'a, T>>; -/// Wrapper around [`std::sync::RwLockWriteGuard`]. -pub type TracingWriteGuard<'a, T> = TracingRwLockGuard<'a, RwLockWriteGuard<'a, T>>; - -impl TracingRwLock { - pub fn new(t: T) -> Self { - Self { - inner: RwLock::new(t), - id: MutexId::new(), + match result { + Ok(inner) => Ok(mapper(inner)), + Err(TryLockError::WouldBlock) => Err(TryLockError::WouldBlock), + Err(TryLockError::Poisoned(poisoned)) => { + Err(PoisonError::new(mapper(poisoned.into_inner())).into()) + } } } - /// Wrapper for [`std::sync::RwLock::read`]. - /// - /// # Panics - /// - /// This method participates in lock dependency tracking. If acquiring this lock introduces a - /// dependency cycle, this method will panic. - #[track_caller] - pub fn read(&self) -> LockResult> { - let mutex = self.id.get_borrowed(); - let result = self.inner.read(); + impl Mutex { + /// Create a new tracing mutex with the provided value. + pub fn new(t: T) -> Self { + Self { + inner: sync::Mutex::new(t), + id: MutexId::new(), + } + } - map_lockresult(result, |inner| TracingRwLockGuard { - inner, - _mutex: mutex, - }) - } + /// Wrapper for [`std::sync::Mutex::lock`]. + /// + /// # Panics + /// + /// This method participates in lock dependency tracking. If acquiring this lock introduces a + /// dependency cycle, this method will panic. + #[track_caller] + pub fn lock(&self) -> LockResult> { + let mutex = self.id.get_borrowed(); + let result = self.inner.lock(); - /// Wrapper for [`std::sync::RwLock::write`]. - /// - /// # Panics - /// - /// This method participates in lock dependency tracking. If acquiring this lock introduces a - /// dependency cycle, this method will panic. - #[track_caller] - pub fn write(&self) -> LockResult> { - let mutex = self.id.get_borrowed(); - let result = self.inner.write(); + let mapper = |guard| MutexGuard { + _mutex: mutex, + inner: guard, + }; - map_lockresult(result, |inner| TracingRwLockGuard { - inner, - _mutex: mutex, - }) - } + map_lockresult(result, mapper) + } - /// Wrapper for [`std::sync::RwLock::try_read`]. - /// - /// # Panics - /// - /// This method participates in lock dependency tracking. If acquiring this lock introduces a - /// dependency cycle, this method will panic. - #[track_caller] - pub fn try_read(&self) -> TryLockResult> { - let mutex = self.id.get_borrowed(); - let result = self.inner.try_read(); + /// Wrapper for [`std::sync::Mutex::try_lock`]. + /// + /// # Panics + /// + /// This method participates in lock dependency tracking. If acquiring this lock introduces a + /// dependency cycle, this method will panic. + #[track_caller] + pub fn try_lock(&self) -> TryLockResult> { + let mutex = self.id.get_borrowed(); + let result = self.inner.try_lock(); - map_trylockresult(result, |inner| TracingRwLockGuard { - inner, - _mutex: mutex, - }) - } + let mapper = |guard| MutexGuard { + _mutex: mutex, + inner: guard, + }; - /// Wrapper for [`std::sync::RwLock::try_write`]. - /// - /// # Panics - /// - /// This method participates in lock dependency tracking. If acquiring this lock introduces a - /// dependency cycle, this method will panic. - #[track_caller] - pub fn try_write(&self) -> TryLockResult> { - let mutex = self.id.get_borrowed(); - let result = self.inner.try_write(); + map_trylockresult(result, mapper) + } - map_trylockresult(result, |inner| TracingRwLockGuard { - inner, - _mutex: mutex, - }) - } + /// Wrapper for [`std::sync::Mutex::is_poisoned`]. + pub fn is_poisoned(&self) -> bool { + self.inner.is_poisoned() + } - /// Return a mutable reference to the underlying data. - /// - /// This method does not block as the locking is handled compile-time by the type system. - pub fn get_mut(&mut self) -> LockResult<&mut T> { - self.inner.get_mut() - } + /// Return a mutable reference to the underlying data. + /// + /// This method does not block as the locking is handled compile-time by the type system. + pub fn get_mut(&mut self) -> LockResult<&mut T> { + self.inner.get_mut() + } - /// Unwrap the mutex and return its inner value. - pub fn into_inner(self) -> LockResult { - self.inner.into_inner() - } -} - -impl From for TracingRwLock { - fn from(t: T) -> Self { - Self::new(t) - } -} - -impl<'a, L, T> Deref for TracingRwLockGuard<'a, L> -where - L: Deref, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - self.inner.deref() - } -} - -impl<'a, T, L> DerefMut for TracingRwLockGuard<'a, L> -where - L: Deref + DerefMut, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.deref_mut() - } -} - -/// Wrapper around [`std::sync::Once`]. -/// -/// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and -/// the one it wraps. -#[derive(Debug)] -pub struct TracingOnce { - inner: Once, - mutex_id: LazyMutexId, -} - -impl TracingOnce { - /// Create a new `Once` value. - pub const fn new() -> Self { - Self { - inner: Once::new(), - mutex_id: LazyMutexId::new(), + /// Unwrap the mutex and return its inner value. + pub fn into_inner(self) -> LockResult { + self.inner.into_inner() } } - /// Wrapper for [`std::sync::Once::call_once`]. + impl From for Mutex { + fn from(t: T) -> Self { + Self::new(t) + } + } + + impl<'a, T> Deref for MutexGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + impl<'a, T> DerefMut for MutexGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + + impl<'a, T: fmt::Display> fmt::Display for MutexGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } + } + + /// Wrapper around [`std::sync::Condvar`]. + /// + /// Allows `TracingMutexGuard` to be used with a `Condvar`. Unlike other structs in this module, + /// this wrapper does not add any additional dependency tracking or other overhead on top of the + /// primitive it wraps. All dependency tracking happens through the mutexes itself. /// /// # Panics /// - /// In addition to the panics that `Once` can cause, this method will panic if calling it - /// introduces a cycle in the lock dependency graph. - pub fn call_once(&self, f: F) - where - F: FnOnce(), - { - let _guard = self.mutex_id.get_borrowed(); - self.inner.call_once(f); + /// This struct does not add any panics over the base implementation of `Condvar`, but panics due to + /// dependency tracking may poison associated mutexes. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use std::thread; + /// + /// use tracing_mutex::stdsync::tracing::{Condvar, Mutex}; + /// + /// let pair = Arc::new((Mutex::new(false), Condvar::new())); + /// let pair2 = Arc::clone(&pair); + /// + /// // Spawn a thread that will unlock the condvar + /// thread::spawn(move || { + /// let (lock, condvar) = &*pair2; + /// *lock.lock().unwrap() = true; + /// condvar.notify_one(); + /// }); + /// + /// // Wait until the thread unlocks the condvar + /// let (lock, condvar) = &*pair; + /// let guard = lock.lock().unwrap(); + /// let guard = condvar.wait_while(guard, |started| !*started).unwrap(); + /// + /// // Guard should read true now + /// assert!(*guard); + /// ``` + #[derive(Debug, Default)] + pub struct Condvar(sync::Condvar); + + impl Condvar { + /// Creates a new condition variable which is ready to be waited on and notified. + pub fn new() -> Self { + Default::default() + } + + /// Wrapper for [`std::sync::Condvar::wait`]. + pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> LockResult> { + let MutexGuard { _mutex, inner } = guard; + + map_lockresult(self.0.wait(inner), |inner| MutexGuard { _mutex, inner }) + } + + /// Wrapper for [`std::sync::Condvar::wait_while`]. + pub fn wait_while<'a, T, F>( + &self, + guard: MutexGuard<'a, T>, + condition: F, + ) -> LockResult> + where + F: FnMut(&mut T) -> bool, + { + let MutexGuard { _mutex, inner } = guard; + + map_lockresult(self.0.wait_while(inner, condition), |inner| MutexGuard { + _mutex, + inner, + }) + } + + /// Wrapper for [`std::sync::Condvar::wait_timeout`]. + pub fn wait_timeout<'a, T>( + &self, + guard: MutexGuard<'a, T>, + dur: Duration, + ) -> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)> { + let MutexGuard { _mutex, inner } = guard; + + map_lockresult(self.0.wait_timeout(inner, dur), |(inner, result)| { + (MutexGuard { _mutex, inner }, result) + }) + } + + /// Wrapper for [`std::sync::Condvar::wait_timeout_while`]. + pub fn wait_timeout_while<'a, T, F>( + &self, + guard: MutexGuard<'a, T>, + dur: Duration, + condition: F, + ) -> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)> + where + F: FnMut(&mut T) -> bool, + { + let MutexGuard { _mutex, inner } = guard; + + map_lockresult( + self.0.wait_timeout_while(inner, dur, condition), + |(inner, result)| (MutexGuard { _mutex, inner }, result), + ) + } + + /// Wrapper for [`std::sync::Condvar::notify_one`]. + pub fn notify_one(&self) { + self.0.notify_one(); + } + + /// Wrapper for [`std::sync::Condvar::notify_all`]. + pub fn notify_all(&self) { + self.0.notify_all(); + } } - /// Performs the same operation as [`call_once`][TracingOnce::call_once] except it ignores - /// poisoning. - /// - /// # Panics - /// - /// This method participates in lock dependency tracking. If acquiring this lock introduces a - /// dependency cycle, this method will panic. - pub fn call_once_force(&self, f: F) - where - F: FnOnce(&OnceState), - { - let _guard = self.mutex_id.get_borrowed(); - self.inner.call_once_force(f); + /// Wrapper for [`std::sync::RwLock`]. + #[derive(Debug, Default)] + pub struct RwLock { + inner: sync::RwLock, + id: MutexId, } - /// Returns true if some `call_once` has completed successfully. - pub fn is_completed(&self) -> bool { - self.inner.is_completed() - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - use std::thread; - - use super::*; - - #[test] - fn test_mutex_usage() { - let mutex = Arc::new(TracingMutex::new(0)); - - assert_eq!(*mutex.lock().unwrap(), 0); - *mutex.lock().unwrap() = 1; - assert_eq!(*mutex.lock().unwrap(), 1); - - let mutex_clone = mutex.clone(); - - let _guard = mutex.lock().unwrap(); - - // Now try to cause a blocking exception in another thread - let handle = thread::spawn(move || { - let result = mutex_clone.try_lock().unwrap_err(); - - assert!(matches!(result, TryLockError::WouldBlock)); - }); - - handle.join().unwrap(); - } - - #[test] - fn test_rwlock_usage() { - let rwlock = Arc::new(TracingRwLock::new(0)); - - assert_eq!(*rwlock.read().unwrap(), 0); - assert_eq!(*rwlock.write().unwrap(), 0); - *rwlock.write().unwrap() = 1; - assert_eq!(*rwlock.read().unwrap(), 1); - assert_eq!(*rwlock.write().unwrap(), 1); - - let rwlock_clone = rwlock.clone(); - - let _read_lock = rwlock.read().unwrap(); - - // Now try to cause a blocking exception in another thread - let handle = thread::spawn(move || { - let write_result = rwlock_clone.try_write().unwrap_err(); - - assert!(matches!(write_result, TryLockError::WouldBlock)); - - // Should be able to get a read lock just fine. - let _read_lock = rwlock_clone.read().unwrap(); - }); - - handle.join().unwrap(); - } - - #[test] - fn test_once_usage() { - let once = Arc::new(TracingOnce::new()); - let once_clone = once.clone(); - - assert!(!once.is_completed()); - - let handle = thread::spawn(move || { - assert!(!once_clone.is_completed()); - - once_clone.call_once(|| {}); - - assert!(once_clone.is_completed()); - }); - - handle.join().unwrap(); - - assert!(once.is_completed()); - } - - #[test] - #[should_panic(expected = "Mutex order graph should not have cycles")] - fn test_detect_cycle() { - let a = TracingMutex::new(()); - let b = TracingMutex::new(()); - - let hold_a = a.lock().unwrap(); - let _ = b.lock(); - - drop(hold_a); - - let _hold_b = b.lock().unwrap(); - let _ = a.lock(); + /// Hybrid wrapper for both [`std::sync::RwLockReadGuard`] and [`std::sync::RwLockWriteGuard`]. + /// + /// Please refer to [`RwLockReadGuard`] and [`RwLockWriteGuard`] for usable types. + #[derive(Debug)] + pub struct TracingRwLockGuard<'a, L> { + inner: L, + _mutex: BorrowedMutex<'a>, + } + + /// Wrapper around [`std::sync::RwLockReadGuard`]. + pub type RwLockReadGuard<'a, T> = TracingRwLockGuard<'a, sync::RwLockReadGuard<'a, T>>; + /// Wrapper around [`std::sync::RwLockWriteGuard`]. + pub type RwLockWriteGuard<'a, T> = TracingRwLockGuard<'a, sync::RwLockWriteGuard<'a, T>>; + + impl RwLock { + pub fn new(t: T) -> Self { + Self { + inner: sync::RwLock::new(t), + id: MutexId::new(), + } + } + + /// Wrapper for [`std::sync::RwLock::read`]. + /// + /// # Panics + /// + /// This method participates in lock dependency tracking. If acquiring this lock introduces a + /// dependency cycle, this method will panic. + #[track_caller] + pub fn read(&self) -> LockResult> { + let mutex = self.id.get_borrowed(); + let result = self.inner.read(); + + map_lockresult(result, |inner| TracingRwLockGuard { + inner, + _mutex: mutex, + }) + } + + /// Wrapper for [`std::sync::RwLock::write`]. + /// + /// # Panics + /// + /// This method participates in lock dependency tracking. If acquiring this lock introduces a + /// dependency cycle, this method will panic. + #[track_caller] + pub fn write(&self) -> LockResult> { + let mutex = self.id.get_borrowed(); + let result = self.inner.write(); + + map_lockresult(result, |inner| TracingRwLockGuard { + inner, + _mutex: mutex, + }) + } + + /// Wrapper for [`std::sync::RwLock::try_read`]. + /// + /// # Panics + /// + /// This method participates in lock dependency tracking. If acquiring this lock introduces a + /// dependency cycle, this method will panic. + #[track_caller] + pub fn try_read(&self) -> TryLockResult> { + let mutex = self.id.get_borrowed(); + let result = self.inner.try_read(); + + map_trylockresult(result, |inner| TracingRwLockGuard { + inner, + _mutex: mutex, + }) + } + + /// Wrapper for [`std::sync::RwLock::try_write`]. + /// + /// # Panics + /// + /// This method participates in lock dependency tracking. If acquiring this lock introduces a + /// dependency cycle, this method will panic. + #[track_caller] + pub fn try_write(&self) -> TryLockResult> { + let mutex = self.id.get_borrowed(); + let result = self.inner.try_write(); + + map_trylockresult(result, |inner| TracingRwLockGuard { + inner, + _mutex: mutex, + }) + } + + /// Return a mutable reference to the underlying data. + /// + /// This method does not block as the locking is handled compile-time by the type system. + pub fn get_mut(&mut self) -> LockResult<&mut T> { + self.inner.get_mut() + } + + /// Unwrap the mutex and return its inner value. + pub fn into_inner(self) -> LockResult { + self.inner.into_inner() + } + } + + impl From for RwLock { + fn from(t: T) -> Self { + Self::new(t) + } + } + + impl<'a, L, T> Deref for TracingRwLockGuard<'a, L> + where + L: Deref, + { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } + } + + impl<'a, T, L> DerefMut for TracingRwLockGuard<'a, L> + where + L: Deref + DerefMut, + { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } + } + + /// Wrapper around [`std::sync::Once`]. + /// + /// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and + /// the one it wraps. + #[derive(Debug)] + pub struct Once { + inner: sync::Once, + mutex_id: LazyMutexId, + } + + impl Once { + /// Create a new `Once` value. + pub const fn new() -> Self { + Self { + inner: sync::Once::new(), + mutex_id: LazyMutexId::new(), + } + } + + /// Wrapper for [`std::sync::Once::call_once`]. + /// + /// # Panics + /// + /// In addition to the panics that `Once` can cause, this method will panic if calling it + /// introduces a cycle in the lock dependency graph. + pub fn call_once(&self, f: F) + where + F: FnOnce(), + { + let _guard = self.mutex_id.get_borrowed(); + self.inner.call_once(f); + } + + /// Performs the same operation as [`call_once`][Once::call_once] except it ignores + /// poisoning. + /// + /// # Panics + /// + /// This method participates in lock dependency tracking. If acquiring this lock introduces a + /// dependency cycle, this method will panic. + pub fn call_once_force(&self, f: F) + where + F: FnOnce(&OnceState), + { + let _guard = self.mutex_id.get_borrowed(); + self.inner.call_once_force(f); + } + + /// Returns true if some `call_once` has completed successfully. + pub fn is_completed(&self) -> bool { + self.inner.is_completed() + } + } + + #[cfg(test)] + mod tests { + use std::sync::Arc; + use std::thread; + + use super::*; + + #[test] + fn test_mutex_usage() { + let mutex = Arc::new(Mutex::new(0)); + + assert_eq!(*mutex.lock().unwrap(), 0); + *mutex.lock().unwrap() = 1; + assert_eq!(*mutex.lock().unwrap(), 1); + + let mutex_clone = mutex.clone(); + + let _guard = mutex.lock().unwrap(); + + // Now try to cause a blocking exception in another thread + let handle = thread::spawn(move || { + let result = mutex_clone.try_lock().unwrap_err(); + + assert!(matches!(result, TryLockError::WouldBlock)); + }); + + handle.join().unwrap(); + } + + #[test] + fn test_rwlock_usage() { + let rwlock = Arc::new(RwLock::new(0)); + + assert_eq!(*rwlock.read().unwrap(), 0); + assert_eq!(*rwlock.write().unwrap(), 0); + *rwlock.write().unwrap() = 1; + assert_eq!(*rwlock.read().unwrap(), 1); + assert_eq!(*rwlock.write().unwrap(), 1); + + let rwlock_clone = rwlock.clone(); + + let _read_lock = rwlock.read().unwrap(); + + // Now try to cause a blocking exception in another thread + let handle = thread::spawn(move || { + let write_result = rwlock_clone.try_write().unwrap_err(); + + assert!(matches!(write_result, TryLockError::WouldBlock)); + + // Should be able to get a read lock just fine. + let _read_lock = rwlock_clone.read().unwrap(); + }); + + handle.join().unwrap(); + } + + #[test] + fn test_once_usage() { + let once = Arc::new(Once::new()); + let once_clone = once.clone(); + + assert!(!once.is_completed()); + + let handle = thread::spawn(move || { + assert!(!once_clone.is_completed()); + + once_clone.call_once(|| {}); + + assert!(once_clone.is_completed()); + }); + + handle.join().unwrap(); + + assert!(once.is_completed()); + } + + #[test] + #[should_panic(expected = "Mutex order graph should not have cycles")] + fn test_detect_cycle() { + let a = Mutex::new(()); + let b = Mutex::new(()); + + let hold_a = a.lock().unwrap(); + let _ = b.lock(); + + drop(hold_a); + + let _hold_b = b.lock().unwrap(); + let _ = a.lock(); + } } }