diff --git a/README.md b/README.md index 876e926..b0460b2 100644 --- a/README.md +++ b/README.md @@ -95,62 +95,65 @@ fn main() { } ``` -## Barging MCS lock +## Thread local MCS queue nodes -This implementation will have non-waiting threads race for the lock against -the front of the waiting queue thread, which means this it is an unfair lock. -This implementation is suitable for `no_std` environments, and the locking -APIs are compatible with the [lock_api] crate. See [`barging`] and [`lock_api`] -modules for more information. +Enables [`raw::Mutex`] locking APIs that operate over queue nodes that are +stored at the thread local storage. These locking APIs require a static +reference to a [`LocalMutexNode`] key. Keys must be generated by the +[`thread_local_node!`] macro. Thread local nodes are not `no_std` compatible +and can be enabled through the `thread_local` feature. ```rust use std::sync::Arc; use std::thread; -// Requires `barging` feature. -use mcslock::barging::spins::Mutex; +use mcslock::raw::spins::Mutex; + +// Requires `thread_local` feature. +mcslock::thread_local_node!(static NODE); fn main() { let mutex = Arc::new(Mutex::new(0)); let c_mutex = Arc::clone(&mutex); thread::spawn(move || { - *c_mutex.lock() = 10; + // Local nodes handles are provided by reference. + // Critical section must be defined as closure. + c_mutex.lock_with_local(&NODE, |mut guard| *guard = 10); }) .join().expect("thread::spawn failed"); - assert_eq!(*mutex.try_lock().unwrap(), 10); + // Local nodes handles are provided by reference. + // Critical section must be defined as closure. + assert_eq!(mutex.try_lock_with_local(&NODE, |g| *g.unwrap()), 10); } ``` -## Thread local MCS lock +## Barging MCS lock -This implementation also operates under FIFO. The locking APIs provided -by this module do not require user-side node allocation, critical -sections must be provided as closures and at most one lock can be held at -any time within a thread. It is not `no_std` compatible and can be enabled -through the `thread_local` feature. See [`thread_local`] module for more -information. +This implementation will have non-waiting threads race for the lock against +the front of the waiting queue thread, which means this it is an unfair lock. +This implementation is suitable for `no_std` environments, and the locking +APIs are compatible with the [lock_api] crate. See [`barging`] and [`lock_api`] +modules for more information. ```rust use std::sync::Arc; use std::thread; -// Requires `thread_local` feature. -use mcslock::thread_local::spins::Mutex; +// Requires `barging` feature. +use mcslock::barging::spins::Mutex; fn main() { let mutex = Arc::new(Mutex::new(0)); let c_mutex = Arc::clone(&mutex); thread::spawn(move || { - // Critical section must be defined as closure. - c_mutex.lock_with(|mut guard| *guard = 10); + *c_mutex.lock() = 10; }) .join().expect("thread::spawn failed"); - // Critical section must be defined as closure. - assert_eq!(mutex.try_lock_with(|guard| *guard.unwrap()), 10); + assert_eq!(*mutex.try_lock().unwrap(), 10); } ``` @@ -168,7 +171,14 @@ of busy-waiting during lock acquisitions and releases, this will call OS scheduler. This may cause a context switch, so you may not want to enable this feature if your intention is to to actually do optimistic spinning. The default implementation calls [`core::hint::spin_loop`], which does in fact -just simply busy-waits. +just simply busy-waits. This feature is not `no_std` compatible. + +### thread_local + +The `thread_local` feature enables [`raw::Mutex`] locking APIs that operate +over queue nodes that are stored at the thread local storage. These locking APIs +require a static reference to a [`LocalMutexNode`] key. Keys must be generated +by the [`thread_local_node!`] macro. This feature is not `no_std` compatible. ### barging @@ -178,15 +188,6 @@ and it is suitable for `no_std` environments. This implementation is not fair (does not guarantee FIFO), but can improve throughput when the lock is heavily contended. -### thread_local - -The `thread_local` feature provides locking APIs that do not require user-side -node allocation, but critical sections must be provided as closures. This -implementation handles the queue's nodes transparently, by storing them in -the thread local storage of the waiting threads. This locking implementation -will panic if more than one guard is alive within a single thread. Not -`no_std` compatible. - ### lock_api This feature implements the [`RawMutex`] trait from the [lock_api] crate for @@ -245,11 +246,13 @@ each of your dependencies, including this one. [cargo-crev]: https://github.com/crev-dev/cargo-crev [`MutexNode`]: https://docs.rs/mcslock/latest/mcslock/raw/struct.MutexNode.html +[`LocalMutexNode`]: https://docs.rs/mcslock/latest/mcslock/raw/struct.LocalMutexNode.html +[`raw::Mutex`]: https://docs.rs/mcslock/latest/mcslock/raw/struct.Mutex.html [`barging::Mutex`]: https://docs.rs/mcslock/latest/mcslock/barging/struct.Mutex.html [`raw`]: https://docs.rs/mcslock/latest/mcslock/raw/index.html [`barging`]: https://docs.rs/mcslock/latest/mcslock/barging/index.html [`lock_api`]: https://docs.rs/mcslock/latest/mcslock/lock_api/index.html -[`thread_local`]: https://docs.rs/mcslock/latest/mcslock/thread_local/index.html +[`thread_local_node!`]: https://docs.rs/mcslock/latest/mcslock/macro.thread_local_node.html [`std::sync::Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html [`parking_lot::Mutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.Mutex.html [`RawMutex`]: https://docs.rs/lock_api/latest/lock_api/trait.RawMutex.html diff --git a/examples/thread_local.rs b/examples/thread_local.rs index bdccced..e991afb 100644 --- a/examples/thread_local.rs +++ b/examples/thread_local.rs @@ -2,8 +2,16 @@ use std::sync::mpsc::channel; use std::sync::Arc; use std::thread; +use mcslock::raw::spins::Mutex; + // Requires that the `thread_local` feature is enabled. -use mcslock::thread_local::spins::Mutex; +mcslock::thread_local_node! { + // * Allows multiple static definitions, must be separated with semicolons. + // * Visibility is optional (private by default). + // * Requires `static` keyword and a UPPER_SNAKE_CASE name. + pub static NODE; + static UNUSED_NODE; +} const N: usize = 10; @@ -27,7 +35,7 @@ fn main() { // threads to ever fail while holding the lock. // // Data is exclusively accessed by the guard argument. - data.lock_with(|mut data| { + data.lock_with_local(&NODE, |mut data| { *data += 1; if *data == N { tx.send(()).unwrap(); @@ -38,6 +46,6 @@ fn main() { } let _message = rx.recv().unwrap(); - let count = data.lock_with(|guard| *guard); + let count = data.lock_with_local(&NODE, |guard| *guard); assert_eq!(count, N); } diff --git a/src/barging/mutex.rs b/src/barging/mutex.rs index 199ccf7..962f42c 100644 --- a/src/barging/mutex.rs +++ b/src/barging/mutex.rs @@ -128,9 +128,7 @@ impl Mutex { /// This function will block the local thread until it is available to acquire /// the mutex. Upon returning, the thread is the only thread with the lock /// held. An RAII guard is returned to allow scoped unlock of the lock. When - /// the guard goes out of scope, the mutex will be unlocked. To acquire a MCS - /// lock, it's also required a mutably borrowed queue node, which is a record - /// that keeps a link for forming the queue, see [`MutexNode`]. + /// the guard goes out of scope, the mutex will be unlocked. /// /// This function will block if the lock is unavailable. /// @@ -203,7 +201,8 @@ impl Mutex { /// assert_eq!(mutex.lock_with(|guard| *guard), 10); /// ``` /// - /// Borrows of the guard or its data cannot escape the given closure. + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: /// /// ```compile_fail,E0515 /// use mcslock::barging::spins::Mutex; @@ -225,9 +224,7 @@ impl Mutex { /// /// If the lock could not be acquired at this time, then [`None`] is returned. /// Otherwise, an RAII guard is returned. The lock will be unlocked when the - /// guard is dropped. To acquire a MCS lock, it's also required a mutably - /// borrowed queue node, which is a record that keeps a link for forming the - /// queue, see [`MutexNode`]. + /// guard is dropped. /// /// This function does not block. /// @@ -293,7 +290,7 @@ impl Mutex { /// if let Some(mut guard) = guard { /// *guard = 10; /// } else { - /// println!("try_lock failed"); + /// println!("try_lock_with failed"); /// } /// }); /// }) @@ -302,7 +299,8 @@ impl Mutex { /// assert_eq!(mutex.lock_with(|guard| *guard), 10); /// ``` /// - /// Borrows of the guard or its data cannot escape the given closure. + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: /// /// ```compile_fail,E0515 /// use mcslock::barging::spins::Mutex; diff --git a/src/cfg.rs b/src/cfg.rs index 510964d..bf3bb5b 100644 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -70,11 +70,16 @@ pub mod hint { pub use loom::hint::spin_loop; } -#[cfg(any(feature = "yield", test))] pub mod thread { - #[cfg(not(all(loom, test)))] + #[cfg(all(any(feature = "yield", test), not(all(loom, test))))] pub use std::thread::yield_now; #[cfg(all(loom, test))] pub use loom::thread::yield_now; + + #[cfg(all(feature = "thread_local", not(all(loom, test))))] + pub use std::thread::LocalKey; + + #[cfg(all(feature = "thread_local", loom, test))] + pub use loom::thread::LocalKey; } diff --git a/src/lib.rs b/src/lib.rs index 8fbe8b2..992ae40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,63 +60,71 @@ //! assert_eq!(*mutex.try_lock(&mut node).unwrap(), 10); //! ``` //! -//! ## Barging MCS lock +//! ## Thread local MCS queue nodes //! -//! This implementation will have non-waiting threads race for the lock against -//! the front of the waiting queue thread, which means this it is an unfair lock. -//! This implementation can be enabled through the `barging` feature, it is -//! suitable for `no_std` environments, and the locking APIs are compatible with -//! the `lock_api` crate. See [`mod@barging`] and [`mod@lock_api`] modules for -//! more information. +//! Enables [`raw::Mutex`] locking APIs that operate over queue nodes that are +//! stored at the thread local storage. These locking APIs require a static +//! reference to a [`LocalMutexNode`] key. Keys must be generated by the +//! [`thread_local_node!`] macro. Thread local nodes are not `no_std` compatible +//! and can be enabled through the `thread_local` feature. //! //! ``` +//! # #[cfg(feature = "thread_local")] +//! # { //! use std::sync::Arc; //! use std::thread; //! -//! use mcslock::barging::spins::Mutex; +//! use mcslock::raw::spins::Mutex; +//! +//! // Requires `thread_local` feature. +//! mcslock::thread_local_node!(static NODE); //! //! let mutex = Arc::new(Mutex::new(0)); //! let c_mutex = Arc::clone(&mutex); //! //! thread::spawn(move || { -//! *c_mutex.lock() = 10; +//! // Local nodes handles are provided by reference. +//! // Critical section must be defined as closure. +//! c_mutex.lock_with_local(&NODE, |mut guard| *guard = 10); //! }) //! .join().expect("thread::spawn failed"); //! -//! assert_eq!(*mutex.try_lock().unwrap(), 10); +//! // Local nodes handles are provided by reference. +//! // Critical section must be defined as closure. +//! assert_eq!(mutex.try_lock_with_local(&NODE, |g| *g.unwrap()), 10); +//! # } +//! # #[cfg(not(feature = "thread_local"))] +//! # fn main() {} //! ``` //! -//! ## Thread local MCS lock +//! ## Barging MCS lock //! -//! This implementation also operates under FIFO. The locking APIs provided -//! by this module do not require user-side node allocation, critical sections -//! must be provided as closures and at most one lock can be held at any time -//! within a thread. It is not `no_std` compatible and can be enabled through -//! the `thread_local` feature. See [`mod@thread_local`] module for more -//! information. +//! This implementation will have non-waiting threads race for the lock against +//! the front of the waiting queue thread, which means this it is an unfair lock. +//! This implementation can be enabled through the `barging` feature, it is +//! suitable for `no_std` environments, and the locking APIs are compatible with +//! the `lock_api` crate. See [`mod@barging`] and [`mod@lock_api`] modules for +//! more information. //! //! ``` -//! # #[cfg(feature = "thread_local")] +//! # #[cfg(feature = "barging")] //! # { //! use std::sync::Arc; //! use std::thread; //! -//! // Requires `thread_local` feature. -//! use mcslock::thread_local::spins::Mutex; +//! use mcslock::barging::spins::Mutex; //! //! let mutex = Arc::new(Mutex::new(0)); //! let c_mutex = Arc::clone(&mutex); //! //! thread::spawn(move || { -//! // Critical section must be defined as closure. -//! c_mutex.lock_with(|mut guard| *guard = 10); +//! *c_mutex.lock() = 10; //! }) //! .join().expect("thread::spawn failed"); //! -//! // Critical section must be defined as closure. -//! assert_eq!(mutex.try_lock_with(|guard| *guard.unwrap()), 10); +//! assert_eq!(*mutex.try_lock().unwrap(), 10); //! # } -//! # #[cfg(not(feature = "thread_local"))] +//! # #[cfg(not(feature = "barging"))] //! # fn main() {} //! ``` //! @@ -134,7 +142,14 @@ //! OS scheduler. This may cause a context switch, so you may not want to enable //! this feature if your intention is to to actually do optimistic spinning. The //! default implementation calls [`core::hint::spin_loop`], which does in fact -//! just simply busy-waits. +//! just simply busy-waits. This feature is not `not_std` compatible. +//! +//! ### thread_local +//! +//! The `thread_local` feature enables [`raw::Mutex`] locking APIs that operate +//! over queue nodes that are stored at the thread local storage. These locking APIs +//! require a static reference to a [`LocalMutexNode`] key. Keys must be generated +//! by the [`thread_local_node!`] macro. This feature is not `no_std` compatible. //! //! ### barging //! @@ -144,15 +159,6 @@ //! fair (does not guarantee FIFO), but can improve throughput when the lock //! is heavily contended. //! -//! ### thread_local -//! -//! The `thread_local` feature provides locking APIs that do not require user-side -//! node allocation, but critical sections must be provided as closures. This -//! implementation handles the queue's nodes transparently, by storing them in -//! the thread local storage of the waiting threads. This locking implementation -//! will panic if more than one guard is alive within a single thread. Not -//! `no_std` compatible. -//! //! ### lock_api //! //! This feature implements the [`RawMutex`] trait from the [lock_api] @@ -169,6 +175,8 @@ //! - libmcs: //! //! [`MutexNode`]: raw::MutexNode +//! [`LocalMutexNode`]: raw::LocalMutexNode +//! [`thread_local_node!`]: crate::thread_local_node //! [`std::sync::Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html //! [`parking_lot::Mutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.Mutex.html //! [`RawMutex`]: https://docs.rs/lock_api/latest/lock_api/trait.RawMutex.html @@ -206,7 +214,7 @@ pub mod lock_api; #[cfg(feature = "thread_local")] #[cfg_attr(docsrs, doc(cfg(feature = "thread_local")))] -pub mod thread_local; +mod thread_local; pub(crate) mod cfg; diff --git a/src/raw/mod.rs b/src/raw/mod.rs index fdf98ad..6fdde21 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -31,6 +31,9 @@ mod mutex; pub use mutex::{Mutex, MutexGuard, MutexNode}; +#[cfg(feature = "thread_local")] +pub use crate::thread_local::LocalMutexNode; + /// A `raw` MCS lock alias that signals the processor that it is running a /// busy-wait spin-loop during lock contention. pub mod spins { diff --git a/src/raw/mutex.rs b/src/raw/mutex.rs index 998bc2e..6cab644 100644 --- a/src/raw/mutex.rs +++ b/src/raw/mutex.rs @@ -54,7 +54,9 @@ impl MutexNodeInit { /// [`try_lock`]: Mutex::try_lock #[repr(transparent)] #[derive(Debug)] -pub struct MutexNode(MaybeUninit); +pub struct MutexNode { + inner: MaybeUninit, +} impl MutexNode { /// Creates new `MutexNode` instance. @@ -69,13 +71,13 @@ impl MutexNode { #[must_use] #[inline(always)] pub const fn new() -> Self { - Self(MaybeUninit::uninit()) + Self { inner: MaybeUninit::uninit() } } /// Initializes this node and returns a exclusive reference to the initialized /// inner state. fn initialize(&mut self) -> &mut MutexNodeInit { - self.0.write(MutexNodeInit::new()) + self.inner.write(MutexNodeInit::new()) } } @@ -144,7 +146,7 @@ impl Default for MutexNode { pub struct Mutex { tail: AtomicPtr, marker: PhantomData, - pub(crate) data: UnsafeCell, + data: UnsafeCell, } // Same unsafe impls as `std::sync::Mutex`. @@ -206,7 +208,9 @@ impl Mutex { /// /// If the lock could not be acquired at this time, then [`None`] is returned. /// Otherwise, an RAII guard is returned. The lock will be unlocked when the - /// guard is dropped. To acquire a MCS lock, it's also required a mutably + /// guard is dropped. + /// + /// To acquire a MCS lock through this function, it's also required a mutably /// borrowed queue node, which is a record that keeps a link for forming the /// queue, see [`MutexNode`]. /// @@ -256,6 +260,11 @@ impl Mutex { /// a [`Some`] value with the mutex guard is given instead. The lock will be /// unlocked when the guard is dropped. /// + /// This function instantiates a [`MutexNode`] for each call, which is + /// convenient for one-liners by not particularly efficient on hot paths. + /// If that is your use case, consider calling [`try_lock`] in busy loops + /// while reusing one single node allocation. + /// /// This function does not block. /// /// ``` @@ -275,7 +284,7 @@ impl Mutex { /// if let Some(mut guard) = guard { /// *guard = 10; /// } else { - /// println!("try_lock failed"); + /// println!("try_lock_with failed"); /// } /// }); /// }) @@ -284,7 +293,8 @@ impl Mutex { /// assert_eq!(mutex.lock_with(|guard| *guard), 10); /// ``` /// - /// Borrows of the guard or its data cannot escape the given closure. + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: /// /// ```compile_fail,E0515 /// use mcslock::raw::spins::Mutex; @@ -292,6 +302,7 @@ impl Mutex { /// let mutex = Mutex::new(1); /// let data = mutex.try_lock_with(|guard| &*guard.unwrap()); /// ``` + /// [`try_lock`]: Mutex::try_lock #[inline] pub fn try_lock_with(&self, f: F) -> Ret where @@ -306,9 +317,11 @@ impl Mutex { /// This function will block the local thread until it is available to acquire /// the mutex. Upon returning, the thread is the only thread with the lock /// held. An RAII guard is returned to allow scoped unlock of the lock. When - /// the guard goes out of scope, the mutex will be unlocked. To acquire a MCS - /// lock, it's also required a mutably borrowed queue node, which is a record - /// that keeps a link for forming the queue, see [`MutexNode`]. + /// the guard goes out of scope, the mutex will be unlocked. + /// + /// To acquire a MCS lock through this function, it's also required a mutably + /// borrowed queue node, which is a record that keeps a link for forming the + /// queue, see [`MutexNode`]. /// /// This function will block if the lock is unavailable. /// @@ -359,6 +372,11 @@ impl Mutex { /// executed against the mutex guard. Once the guard goes out of scope, it /// will unlock the mutex. /// + /// This function instantiates a [`MutexNode`] for each call, which is + /// convenient for one-liners by not particularly efficient on hot paths. + /// If that is your use case, consider calling [`lock`] in the busy loop + /// while reusing one single node allocation. + /// /// This function will block if the lock is unavailable. /// /// # Examples @@ -383,7 +401,8 @@ impl Mutex { /// assert_eq!(mutex.lock_with(|guard| *guard), 10); /// ``` /// - /// Borrows of the guard or its data cannot escape the given closure. + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: /// /// ```compile_fail,E0515 /// use mcslock::raw::spins::Mutex; @@ -391,6 +410,7 @@ impl Mutex { /// let mutex = Mutex::new(1); /// let data = mutex.lock_with(|guard| &*guard); /// ``` + /// [`lock`]: Mutex::lock #[inline] pub fn lock_with(&self, f: F) -> Ret where @@ -662,22 +682,28 @@ unsafe impl crate::loom::Guard for MutexGuard<'_, T, R> { #[cfg(all(not(loom), test))] mod test { + use super::{MutexNode, MutexNodeInit}; + use crate::raw::yields::Mutex; use crate::test::tests; #[test] - fn node_default_and_new_init() { - use super::MutexNode; + fn node_drop_does_not_matter() { + assert!(!core::mem::needs_drop::()); + assert!(!core::mem::needs_drop::()); + } + #[test] + fn node_default_and_new_init() { let mut d = MutexNode::default(); - let dinit = d.initialize(); - assert!(dinit.next.get_mut().is_null()); - assert!(*dinit.locked.get_mut()); + let d_init = d.initialize(); + assert!(d_init.next.get_mut().is_null()); + assert!(*d_init.locked.get_mut()); let mut n = MutexNode::new(); - let ninit = n.initialize(); - assert!(ninit.next.get_mut().is_null()); - assert!(*ninit.locked.get_mut()); + let n_init = n.initialize(); + assert!(n_init.next.get_mut().is_null()); + assert!(*n_init.locked.get_mut()); } #[test] diff --git a/src/thread_local.rs b/src/thread_local.rs new file mode 100644 index 0000000..1526a50 --- /dev/null +++ b/src/thread_local.rs @@ -0,0 +1,805 @@ +use core::cell::{RefCell, RefMut}; +use core::panic::Location; + +use crate::cfg::thread::LocalKey; +use crate::raw::{Mutex, MutexGuard, MutexNode}; +use crate::relax::Relax; + +/// A handle to a [`MutexNode`] stored at the thread local storage. +/// +/// Thread local nodes can be claimed for temporary, exclusive access during +/// runtime for locking purposes. Node handles refer to the node stored at +/// the current running thread. +/// +/// Just like `MutexNode`, this is an opaque type that holds metadata for the +/// [`raw::Mutex`]'s waiting queue. You must declare a thread local node with +/// the [`thread_local_node!`] macro, and provide the generated handle to the +/// appropriate [`raw::Mutex`] locking APIs. Attempting to lock a mutex with a +/// thread local node that already is in used for the locking thread will cause +/// a panic. Handles are provided by reference to functions. +/// +/// See: [`try_lock_with_local`], [`lock_with_local`], +/// [`try_lock_with_local_unchecked`] or [`lock_with_local_unchecked`]. +/// +/// [`MutexNode`]: MutexNode +/// [`raw::Mutex`]: Mutex +/// [`thread_local_node!`]: crate::thread_local_node +/// [`try_lock_with_local`]: Mutex::try_lock_with_local +/// [`lock_with_local`]: Mutex::lock_with_local +/// [`try_lock_with_local_unchecked`]: Mutex::try_lock_with_local_unchecked +/// [`lock_with_local_unchecked`]: Mutex::lock_with_local_unchecked +#[repr(transparent)] +#[derive(Debug)] +pub struct LocalMutexNode { + #[cfg(not(all(loom, test)))] + key: LocalKey>, + // We can't take ownership of Loom's `thread_local!` value since it is a + // `static`, non-copy value, so we just point to it. + #[cfg(all(loom, test))] + key: &'static LocalKey>, +} + +impl LocalMutexNode { + /// Creates a new `LocalMutexNode` key from the provided thread local node + /// key. + /// + /// This function is **NOT** part of the public API and so must not be + /// called directly by user's code. It is subjected to changes **WITHOUT** + /// prior notice or accompanied with relevant SemVer changes. + #[cfg(not(all(loom, test)))] + #[cfg(not(tarpaulin_include))] + #[doc(hidden)] + #[must_use] + #[inline(always)] + pub const fn __new(key: LocalKey>) -> Self { + Self { key } + } +} + +/// Non-recursive definition of `thread_local_node!`. +/// +/// This macro is **NOT** part of the public API and so must not be called +/// directly by user's code. It is subjected to changes **WITHOUT** prior +/// notice or accompanied with relevant SemVer changes. +#[cfg(not(all(loom, test)))] +#[doc(hidden)] +#[macro_export] +macro_rules! __thread_local_node_inner { + ($vis:vis $node:ident) => { + $vis const $node: $crate::raw::LocalMutexNode = { + ::std::thread_local! { + static NODE: ::core::cell::RefCell<$crate::raw::MutexNode> = const { + ::core::cell::RefCell::new($crate::raw::MutexNode::new()) + }; + } + $crate::raw::LocalMutexNode::__new(NODE) + }; + }; +} + +/// Non-recursive, Loom based definition of `thread_local_node!`. +/// +/// This node definition uses Loom primitives and it can't be evaluated at +/// compile-time since Loom does not support that feature. Loom's `thread_local!` +/// macro defines a `static` value as oppose to std's `const` value. +#[cfg(all(loom, test))] +#[macro_export] +macro_rules! __thread_local_node_inner { + ($vis:vis $node:ident) => { + $vis static $node: $crate::raw::LocalMutexNode = { + ::loom::thread_local! { + static NODE: ::core::cell::RefCell<$crate::raw::MutexNode> = { + ::core::cell::RefCell::new($crate::raw::MutexNode::new()) + }; + } + $crate::raw::LocalMutexNode { key: &NODE } + }; + }; +} + +/// Declares a new [`LocalMutexNode`] key, which is a handle to the thread local +/// node of the currently running thread. +/// +/// The macro wraps any number of static declarations and make them thread +/// local. Each provided name is associated with a single thread local key. The +/// keys are wrapped and managed by the [`LocalMutexNode`] type, which are the +/// actual handles meant to be used with the `lock_with_local` API family from +/// [`raw::Mutex`]. Handles are provided by reference to functions. +/// +/// See: [`try_lock_with_local`], [`lock_with_local`], +/// [`try_lock_with_local_unchecked`] or [`lock_with_local_unchecked`]. +/// +/// The thread local node definition generated by this macro avoids lazy +/// initialization and does not need to be dropped, which enables a more +/// efficient underlying implementation. See [`std::thread_local!`] macro. +/// +/// # Sintax +/// +/// * Allows multiple static definitions, must be separated with semicolons. +/// * Visibility is optional (private by default). +/// * Requires `static` keyword and a **UPPER_SNAKE_CASE** name. +/// +/// # Example +/// +/// ``` +/// use mcslock::raw::spins::Mutex; +/// +/// // Multiple difenitions. +/// mcslock::thread_local_node! { +/// pub static NODE; +/// static OTHER_NODE1; +/// } +/// +/// // Single definition. +/// mcslock::thread_local_node!(pub static OTHER_NODE2); +/// +/// let mutex = Mutex::new(0); +/// // Keys are provided to APIs by reference. +/// mutex.lock_with_local(&NODE, |mut guard| *guard = 10); +/// assert_eq!(mutex.lock_with_local(&NODE, |guard| *guard), 10); +/// ``` +/// [`raw::Mutex`]: Mutex +/// [`std::thread_local!`]: https://doc.rust-lang.org/std/macro.thread_local.html +/// [`try_lock_with_local`]: Mutex::try_lock_with_local +/// [`lock_with_local`]: Mutex::lock_with_local +/// [`try_lock_with_local_unchecked`]: Mutex::try_lock_with_local_unchecked +/// [`lock_with_local_unchecked`]: Mutex::lock_with_local_unchecked +#[macro_export] +macro_rules! thread_local_node { + // Empty (base for recursion). + () => {}; + // Process multiply definitions (recursive). + ($vis:vis static $node:ident; $($rest:tt)*) => { + $crate::__thread_local_node_inner!($vis $node); + $crate::thread_local_node!($($rest)*); + }; + // Process single declaration. + ($vis:vis static $node:ident) => { + $crate::__thread_local_node_inner!($vis $node); + }; +} + +/// The local node error message as a string literal. +macro_rules! already_borrowed_error { + () => { + "thread local MCS lock node is already mutably borrowed" + }; +} + +/// Panics the thread with a message pointing to the panic location. +#[inline(never)] +#[cold] +fn panic_already_borrowed(caller: &Location<'static>) -> ! { + panic!("{}, conflict at: {}", already_borrowed_error!(), caller) +} + +impl Mutex { + /// Attempts to acquire this mutex and then runs a closure against its guard. + /// + /// If the lock could not be acquired at this time, then a [`None`] value is + /// given back as the closure argument. If the lock has been acquired, then + /// a [`Some`] value with the mutex guard is given instead. The lock will be + /// unlocked when the guard is dropped. + /// + /// To acquire a MCS lock through this function, it's also required a + /// queue node, which is a record that keeps a link for forming the queue, + /// to be stored in the current locking thread local storage. See + /// [`LocalMutexNode`] and [`thread_local_node!`]. + /// + /// This function does not block. + /// + /// # Panics + /// + /// Will panic if the thread local node is already mutably borrowed. + /// + /// Panics if the key currently has its destructor running, and it **may** + /// panic if the destructor has previously been run for this thread. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use std::thread; + /// + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Arc::new(Mutex::new(0)); + /// let c_mutex = Arc::clone(&mutex); + /// + /// thread::spawn(move || { + /// c_mutex.try_lock_with_local(&NODE, |guard| { + /// if let Some(mut guard) = guard { + /// *guard = 10; + /// } else { + /// println!("try_lock_with_local failed"); + /// } + /// }); + /// }) + /// .join().expect("thread::spawn failed"); + /// + /// assert_eq!(mutex.lock_with_local(&NODE, |guard| *guard), 10); + /// ``` + /// + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: + /// + /// ```compile_fail,E0515 + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(1); + /// let data = mutex.try_lock_with_local(&NODE, |guard| &*guard.unwrap()); + /// ``` + /// + /// Panic: thread local node cannot be borrowed more than once at the same + /// time: + /// + #[doc = concat!("```should_panic,", already_borrowed_error!())] + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(0); + /// + /// mutex.lock_with_local(&NODE, |_guard| { + /// // `NODE` is already mutably borrowed in this thread by the + /// // enclosing `lock_with_local`, the borrow is live for the full + /// // duration of this closure scope. + /// let mutex = Mutex::new(()); + /// mutex.try_lock_with_local(&NODE, |_guard| ()); + /// }); + /// ``` + /// [`LocalMutexNode`]: LocalMutexNode + /// [`thread_local_node!`]: crate::thread_local_node + #[inline] + #[track_caller] + pub fn try_lock_with_local(&self, node: &'static LocalMutexNode, f: F) -> Ret + where + F: FnOnce(Option>) -> Ret, + { + self.with_local_node(node, |mutex, node| f(mutex.try_lock(node))) + } + + /// Attempts to acquire this mutex and then runs a closure against its guard. + /// + /// If the lock could not be acquired at this time, then a [`None`] value is + /// given back as the closure argument. If the lock has been acquired, then + /// a [`Some`] value with the mutex guard is given instead. The lock will be + /// unlocked when the guard is dropped. + /// + /// To acquire a MCS lock through this function, it's also required a + /// queue node, which is a record that keeps a link for forming the queue, + /// to be stored in the current locking thread local storage. See + /// [`LocalMutexNode`] and [`thread_local_node!`]. + /// + /// This function does not block. + /// + /// # Safety + /// + /// Unlike [`try_lock_with_local`], this method is unsafe because it does + /// not check if the current thread local node is already mutably borrowed. + /// If the current thread local node is already borrowed, calling this + /// function is undefined behavior. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use std::thread; + /// + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Arc::new(Mutex::new(0)); + /// let c_mutex = Arc::clone(&mutex); + /// + /// thread::spawn(move || unsafe { + /// c_mutex.try_lock_with_local_unchecked(&NODE, |guard| { + /// if let Some(mut guard) = guard { + /// *guard = 10; + /// } else { + /// println!("try_lock_with_local_unchecked failed"); + /// } + /// }); + /// }) + /// .join().expect("thread::spawn failed"); + /// + /// assert_eq!(mutex.lock_with_local(&NODE, |guard| *guard), 10); + /// ``` + /// + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: + /// + /// ```compile_fail,E0515 + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(1); + /// let data = unsafe { + /// mutex.try_lock_with_local_unchecked(&NODE, |g| &*g.unwrap()) + /// }; + /// ``` + /// + /// Undefined behavior: thread local node cannot be borrowed more than once + /// at the same time: + /// + /// ```no_run + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(0); + /// + /// mutex.lock_with_local(&NODE, |_guard| unsafe { + /// // UB: `NODE` is already mutably borrowed in this thread by the + /// // enclosing `lock_with_local`, the borrow is live for the full + /// // duration of this closure scope. + /// let mutex = Mutex::new(()); + /// mutex.try_lock_with_local_unchecked(&NODE, |_guard| ()); + /// }); + /// ``` + /// [`try_lock_with_local`]: Mutex::try_lock_with_local + pub unsafe fn try_lock_with_local_unchecked( + &self, + node: &'static LocalMutexNode, + f: F, + ) -> Ret + where + F: FnOnce(Option>) -> Ret, + { + self.with_local_node_unchecked(node, |mutex, node| f(mutex.try_lock(node))) + } + + /// Acquires this mutex and then runs the closure against its guard. + /// + /// This function will block the local thread until it is available to acquire + /// the mutex. Upon acquiring the mutex, the user provided closure will be + /// executed against the mutex guard. Once the guard goes out of scope, it + /// will unlock the mutex. + /// + /// To acquire a MCS lock through this function, it's also required a + /// queue node, which is a record that keeps a link for forming the queue, + /// to be stored in the current locking thread local storage. See + /// [`LocalMutexNode`] and [`thread_local_node!`]. + /// + /// This function will block if the lock is unavailable. + /// + /// # Panics + /// + /// Will panic if the thread local node is already mutably borrowed. + /// + /// Panics if the key currently has its destructor running, and it **may** + /// panic if the destructor has previously been run for this thread. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use std::thread; + /// + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Arc::new(Mutex::new(0)); + /// let c_mutex = Arc::clone(&mutex); + /// + /// thread::spawn(move || { + /// c_mutex.lock_with_local(&NODE, |mut guard| *guard = 10); + /// }) + /// .join().expect("thread::spawn failed"); + /// + /// assert_eq!(mutex.lock_with_local(&NODE, |guard| *guard), 10); + /// ``` + /// + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: + /// + /// ```compile_fail,E0515 + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(1); + /// let data = mutex.lock_with_local(&NODE, |guard| &*guard); + /// ``` + /// + /// Panic: thread local node cannot be borrowed more than once at the same + /// time: + /// + #[doc = concat!("```should_panic,", already_borrowed_error!())] + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(0); + /// + /// mutex.lock_with_local(&NODE, |_guard| { + /// // `NODE` is already mutably borrowed in this thread by the + /// // enclosing `lock_with_local`, the borrow is live for the full + /// // duration of this closure scope. + /// let mutex = Mutex::new(()); + /// mutex.lock_with_local(&NODE, |_guard| ()); + /// }); + /// ``` + /// [`LocalMutexNode`]: LocalMutexNode + /// [`thread_local_node!`]: crate::thread_local_node + #[inline] + #[track_caller] + pub fn lock_with_local(&self, node: &'static LocalMutexNode, f: F) -> Ret + where + F: FnOnce(MutexGuard<'_, T, R>) -> Ret, + { + self.with_local_node(node, |mutex, node| f(mutex.lock(node))) + } + + /// Acquires this mutex and then runs the closure against its guard. + /// + /// This function will block the local thread until it is available to acquire + /// the mutex. Upon acquiring the mutex, the user provided closure will be + /// executed against the mutex guard. Once the guard goes out of scope, it + /// will unlock the mutex. + /// + /// To acquire a MCS lock through this function, it's also required a + /// queue node, which is a record that keeps a link for forming the queue, + /// to be stored in the current locking thread local storage. See + /// [`LocalMutexNode`] and [`thread_local_node!`]. + /// + /// This function will block if the lock is unavailable. + /// + /// # Safety + /// + /// Unlike [`lock_with_local`], this method is unsafe because it does not + /// check if the current thread local node is already mutably borrowed. If + /// the current thread local node is already borrowed, calling this + /// function is undefined behavior. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use std::thread; + /// + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Arc::new(Mutex::new(0)); + /// let c_mutex = Arc::clone(&mutex); + /// + /// thread::spawn(move || unsafe { + /// c_mutex.lock_with_local_unchecked(&NODE, |mut guard| *guard = 10); + /// }) + /// .join().expect("thread::spawn failed"); + /// + /// assert_eq!(mutex.lock_with_local(&NODE, |guard| *guard), 10); + /// ``` + /// + /// Compile fail: borrows of the guard or its data cannot escape the given + /// closure: + /// + /// ```compile_fail,E0515 + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(1); + /// let data = unsafe { + /// mutex.lock_with_local_unchecked(&NODE, |g| &*g) + /// }; + /// ``` + /// + /// Undefined behavior: thread local node cannot be borrowed more than once + /// at the same time: + /// + /// ```no_run + /// use mcslock::raw::spins::Mutex; + /// + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(0); + /// + /// mutex.lock_with_local(&NODE, |_guard| unsafe { + /// // UB: `NODE` is already mutably borrowed in this thread by the + /// // enclosing `lock_with_local`, the borrow is live for the full + /// // duration of this closure scope. + /// let mutex = Mutex::new(()); + /// mutex.lock_with_local_unchecked(&NODE, |_guard| ()); + /// }); + /// ``` + /// [`lock_with_local`]: Mutex::lock_with_local + #[inline] + pub unsafe fn lock_with_local_unchecked( + &self, + node: &'static LocalMutexNode, + f: F, + ) -> Ret + where + F: FnOnce(MutexGuard<'_, T, R>) -> Ret, + { + self.with_local_node_unchecked(node, |mutex, node| f(mutex.lock(node))) + } + + /// Guard cannot outlive the closure or else it would allow the guard drop + /// call to access the thread local node even though its exclusive borrow + /// has already expired at the end of the closure. + /// + /// ```compile_fail + /// use mcslock::raw::spins::Mutex; + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(1); + /// let guard = mutex.lock_with_local(&NODE, |guard| guard); + /// ``` + /// + /// ```compile_fail,E0521 + /// use std::thread; + /// use mcslock::raw::spins::Mutex; + /// mcslock::thread_local_node!(static NODE); + /// + /// let mutex = Mutex::new(1); + /// mutex.lock_with_local(&NODE, |guard| { + /// thread::spawn(move || { + /// let guard = guard; + /// }); + /// }); + /// ``` + #[cfg(not(tarpaulin_include))] + const fn __guard_cant_escape_closure() {} +} + +impl Mutex { + /// Runs `f` over a raw mutex and a thread local node as arguments. + /// + /// # Panics + /// + /// Will panic if the thread local node is already mutably borrowed. + /// + /// Panics if the key currently has its destructor running, and it **may** + /// panic if the destructor has previously been run for this thread. + #[track_caller] + fn with_local_node(&self, node: &'static LocalMutexNode, f: F) -> Ret + where + F: FnOnce(&Self, &mut MutexNode) -> Ret, + { + let caller = Location::caller(); + let panic = |_| panic_already_borrowed(caller); + let f = |mut node: RefMut<_>| f(self, &mut node); + node.key.with(|node| node.try_borrow_mut().map_or_else(panic, f)) + } + + /// Runs 'f' over the a raw mutex and thread local node as arguments without + /// checking if the node is currently mutably borrowed. + /// + /// # Safety + /// + /// Mutably borrowing a [`RefCell`] while references are still live is + /// undefined behaviour. Threfore, caller must guarantee that the thread + /// local node is not already in use for the current thread. A thread local + /// node is release to the current thread once the associated `with_local`'s + /// f closure runs out of scope. + unsafe fn with_local_node_unchecked(&self, node: &'static LocalMutexNode, f: F) -> Ret + where + F: FnOnce(&Self, &mut MutexNode) -> Ret, + { + // SAFETY: Caller guaranteed that no other references are live. + node.key.with(|node| f(self, unsafe { &mut *node.as_ptr() })) + } +} + +// A thread local node definition used for testing. +#[cfg(test)] +#[cfg(not(tarpaulin_include))] +thread_local_node!(static TEST_NODE); + +/// A Mutex wrapper type that calls the `lock_with_local` and +/// `try_lock_with_local` when implementing testing traits. +#[cfg(test)] +struct MutexPanic(Mutex); + +#[cfg(test)] +impl crate::test::LockNew for MutexPanic { + type Target = T; + + fn new(value: Self::Target) -> Self + where + Self::Target: Sized, + { + Self(Mutex::new(value)) + } +} + +#[cfg(test)] +impl crate::test::LockWith for MutexPanic { + type Guard<'a> = MutexGuard<'a, Self::Target, R> + where + Self: 'a, + Self::Target: 'a; + + fn try_lock_with(&self, f: F) -> Ret + where + F: FnOnce(Option>) -> Ret, + { + self.0.try_lock_with_local(&TEST_NODE, f) + } + + fn lock_with(&self, f: F) -> Ret + where + F: FnOnce(MutexGuard<'_, T, R>) -> Ret, + { + self.0.lock_with_local(&TEST_NODE, f) + } + + fn is_locked(&self) -> bool { + self.0.is_locked() + } +} + +/// A Mutex wrapper type that calls the `lock_with_local_unchecked` and +/// `try_lock_with_local_unchecked` when implementing testing traits. +#[cfg(test)] +struct MutexUnchecked(Mutex); + +#[cfg(test)] +impl crate::test::LockNew for MutexUnchecked { + type Target = T; + + fn new(value: Self::Target) -> Self + where + Self::Target: Sized, + { + Self(Mutex::new(value)) + } +} + +#[cfg(test)] +impl crate::test::LockWith for MutexUnchecked { + type Guard<'a> = MutexGuard<'a, Self::Target, R> + where + Self: 'a, + Self::Target: 'a; + + fn try_lock_with(&self, f: F) -> Ret + where + F: FnOnce(Option>) -> Ret, + { + // SAFETY: caller must guarantee that this thread local NODE is not + // already mutably borrowed for some other lock acquisition. + unsafe { self.0.try_lock_with_local_unchecked(&TEST_NODE, f) } + } + + fn lock_with(&self, f: F) -> Ret + where + F: FnOnce(MutexGuard<'_, T, R>) -> Ret, + { + // SAFETY: caller must guarantee that this thread local NODE is not + // already mutably borrowed for some other lock acquisition. + unsafe { self.0.lock_with_local_unchecked(&TEST_NODE, f) } + } + + fn is_locked(&self) -> bool { + self.0.is_locked() + } +} + +#[cfg(all(not(loom), test))] +mod test { + use crate::raw::MutexNode; + use crate::relax::Yield; + use crate::test::tests; + + type MutexPanic = super::MutexPanic; + type MutexUnchecked = super::MutexUnchecked; + + #[test] + fn ref_cell_node_drop_does_not_matter() { + use core::{cell::RefCell, mem}; + assert!(!mem::needs_drop::>()); + } + + #[test] + fn lots_and_lots() { + tests::lots_and_lots::>(); + } + + #[test] + fn lots_and_lots_unchecked() { + tests::lots_and_lots::>(); + } + + #[test] + fn smoke() { + tests::smoke::>(); + } + + #[test] + fn smoke_unchecked() { + tests::smoke::>(); + } + + #[test] + fn test_try_lock() { + tests::test_try_lock::>(); + } + + #[test] + fn test_try_lock_unchecked() { + tests::test_try_lock::>(); + } + + #[test] + #[should_panic = already_borrowed_error!()] + fn test_lock_arc_nested() { + tests::test_lock_arc_nested::, MutexPanic<_>>(); + } + + #[test] + #[should_panic = already_borrowed_error!()] + fn test_acquire_more_than_one_lock() { + tests::test_acquire_more_than_one_lock::>(); + } + + #[test] + fn test_lock_arc_access_in_unwind() { + tests::test_lock_arc_access_in_unwind::>(); + } + + #[test] + fn test_lock_arc_access_in_unwind_unchecked() { + tests::test_lock_arc_access_in_unwind::>(); + } + + #[test] + fn test_lock_unsized() { + tests::test_lock_unsized::>(); + } + + #[test] + fn test_lock_unsized_unchecked() { + tests::test_lock_unsized::>(); + } +} + +#[cfg(all(loom, test))] +mod model { + use crate::loom::models; + use crate::relax::Yield; + + type MutexPanic = super::MutexPanic; + type MutexUnchecked = super::MutexUnchecked; + + #[test] + fn try_lock_join() { + models::try_lock_join::>(); + } + + #[test] + fn try_lock_join_unchecked() { + models::try_lock_join::>(); + } + + #[test] + fn lock_join() { + models::lock_join::>(); + } + + #[test] + fn lock_join_unchecked() { + models::lock_join::>(); + } + + #[test] + fn mixed_lock_join() { + models::mixed_lock_join::>(); + } + + #[test] + fn mixed_lock_join_unchecked() { + models::mixed_lock_join::>(); + } +} diff --git a/src/thread_local/mod.rs b/src/thread_local/mod.rs deleted file mode 100644 index eb39c78..0000000 --- a/src/thread_local/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! A MCS lock implementation that stores queue nodes in the thread local -//! storage of the waiting threads. -//! -//! The `thread_local` implementation of MCS lock is fair, that is, it -//! guarantees that thread that have waited for longer will be scheduled first -//! (FIFO). Each waiting thread will spin against its own, thread local atomic -//! lock state, which then avoids the network contention of the state access. -//! -//! This module provide MCS locking APIs that do not require user-side node -//! allocation, by managing the queue's node allocations internally. Queue -//! nodes are stored in the thread local storage, therefore this implementation -//! requires support from the standard library. Critical sections must be -//! provided to [`lock_with`] and [`try_lock_with`] as closures. Closure arguments -//! provide a RAII guard that has exclusive over the data. The mutex guard will -//! be dropped at the end of the call, freeing the mutex. -//! -//! The Mutex is generic over the relax strategy. User may choose a strategy -//! as long as it implements the [`Relax`] trait. There is a number of strategies -//! provided by the [`relax`] module. Each module in `thread_local` provides type -//! aliases for [`Mutex`] and [`MutexGuard`] associated with one relax strategy. -//! See their documentation for more information. -//! -//! # Panics -//! -//! The `thread_local` [`Mutex`] implementation only allows at most on lock held -//! within a single thread at any time. Trying to acquire a second lock while a -//! guard is alive will cause a panic. See [`lock_with`] and [`try_lock_with`] -//! functions for more information. -//! -//! [`lock_with`]: Mutex::lock_with -//! [`try_lock_with`]: Mutex::try_lock_with -//! [`relax`]: crate::relax -//! [`Relax`]: crate::relax::Relax - -mod mutex; -pub use mutex::{Mutex, MutexGuard}; - -/// A `thread_local` MCS lock alias that signals the processor that it is running -/// a busy-wait spin-loop during lock contention. -pub mod spins { - use super::mutex; - use crate::relax::Spin; - - /// A `thread_local` MCS lock that implements the [`Spin`] relax strategy. - /// - /// # Example - /// - /// ``` - /// use mcslock::thread_local::spins::Mutex; - /// - /// let mutex = Mutex::new(0); - /// let data = mutex.lock_with(|guard| *guard); - /// assert_eq!(data, 0); - /// ``` - pub type Mutex = mutex::Mutex; - - /// A `thread_local` MCS guard that implements the [`Spin`] relax strategy. - pub type MutexGuard<'a, T> = mutex::MutexGuard<'a, T, Spin>; - - /// A `thread_local` MCS lock alias that, during lock contention, will perform - /// exponential backoff while signaling the processor that it is running a - /// busy-wait spin-loop. - pub mod backoff { - use super::mutex; - use crate::relax::SpinBackoff; - - /// A `thread_local` MCS lock that implements the [`SpinBackoff`] relax - /// strategy. - /// - /// # Example - /// - /// ``` - /// use mcslock::thread_local::spins::backoff::Mutex; - /// - /// let mutex = Mutex::new(0); - /// let data = mutex.lock_with(|guard| *guard); - /// assert_eq!(data, 0); - /// ``` - pub type Mutex = mutex::Mutex; - - /// A `thread_local` MCS guard that implements the [`SpinBackoff`] relax - /// strategy. - pub type MutexGuard<'a, T> = mutex::MutexGuard<'a, T, SpinBackoff>; - } -} - -/// A `thread_local` MCS lock alias that yields the current time slice to the -/// OS scheduler during lock contention. -#[cfg(any(feature = "yield", loom, test))] -#[cfg_attr(docsrs, doc(cfg(feature = "yield")))] -pub mod yields { - use super::mutex; - use crate::relax::Yield; - - /// A `thread_local` MCS lock that implements the [`Yield`] relax strategy. - /// - /// # Example - /// - /// ``` - /// use mcslock::thread_local::yields::Mutex; - /// - /// let mutex = Mutex::new(0); - /// let data = mutex.lock_with(|guard| *guard); - /// assert_eq!(data, 0); - /// ``` - pub type Mutex = mutex::Mutex; - - /// A `thread_local` MCS guard that implements the [`Yield`] relax strategy. - pub type MutexGuard<'a, T> = mutex::MutexGuard<'a, T, Yield>; - - /// A `thread_local` MCS lock alias that, during lock contention, will perform - /// exponential backoff while spinning up to a threshold, then yields back to - /// the OS scheduler. - #[cfg(feature = "yield")] - pub mod backoff { - use super::mutex; - use crate::relax::YieldBackoff; - - /// A `thread_local` MCS lock that implements the [`YieldBackoff`] relax - /// strategy. - /// - /// # Example - /// - /// ``` - /// use mcslock::thread_local::yields::backoff::Mutex; - /// - /// let mutex = Mutex::new(0); - /// let data = mutex.lock_with(|guard| *guard); - /// assert_eq!(data, 0); - /// ``` - pub type Mutex = mutex::Mutex; - - /// A `thread_local` MCS guard that implements the [`YieldBackoff`] relax - /// strategy. - pub type MutexGuard<'a, T> = mutex::MutexGuard<'a, T, YieldBackoff>; - } -} - -/// A `thread_local` MCS lock alias that rapidly spins without telling the CPU -/// to do any power down during lock contention. -pub mod loops { - use super::mutex; - use crate::relax::Loop; - - /// A `thread_local` MCS lock that implements the [`Loop`] relax strategy. - /// - /// # Example - /// - /// ``` - /// use mcslock::thread_local::loops::Mutex; - /// - /// let mutex = Mutex::new(0); - /// let data = mutex.lock_with(|guard| *guard); - /// assert_eq!(data, 0); - /// ``` - pub type Mutex = mutex::Mutex; - - /// A `thread_local` MCS guard that implements the [`Loop`] relax strategy. - pub type MutexGuard<'a, T> = mutex::MutexGuard<'a, T, Loop>; -} diff --git a/src/thread_local/mutex.rs b/src/thread_local/mutex.rs deleted file mode 100644 index 1fad68c..0000000 --- a/src/thread_local/mutex.rs +++ /dev/null @@ -1,912 +0,0 @@ -use core::cell::{RefCell, RefMut}; -use core::fmt::{self, Debug, Display}; -use core::marker::PhantomData; -use core::panic::Location; - -use crate::raw::{Mutex as RawMutex, MutexGuard as RawMutexGuard, MutexNode}; -use crate::relax::Relax; - -#[cfg(test)] -use crate::test::{LockNew, LockWith}; - -// A thread_local key holding a [`MutexNode`] instance behind a [`RefCell`]. -// -// This node definition can be evaluated as constant. -#[cfg(not(all(loom, test)))] -std::thread_local! { - static NODE: RefCell = const { - RefCell::new(MutexNode::new()) - } -} - -// A Loom's thread_local key holding a [`MutexNode`] instance behind a -// [`RefCell`]. -// -// This node definition uses Loom primitives and it can't be evaluated at -// compile-time since Loom does not support that feature. -#[cfg(all(loom, test))] -#[cfg(not(tarpaulin_include))] -loom::thread_local! { - static NODE: RefCell = { - RefCell::new(MutexNode::new()) - } -} - -/// The panic message as a string literal. -macro_rules! literal_panic_msg { - () => { - "a thread local MCS lock is already held by this thread" - }; -} - -/// Panics the thread with a message pointing to the panic location. -#[inline(never)] -#[cold] -fn panic_already_held(caller: &Location<'static>) -> ! { - panic!("{}, conflict at: {}", literal_panic_msg!(), caller) -} - -/// A mutual exclusion primitive useful for protecting shared data. -/// -/// This mutex will block threads waiting for the lock to become available. The -/// mutex can also be statically initialized or created via a [`new`] -/// constructor. Each mutex has a type parameter which represents the data that -/// it is protecting. The data can only be accessed through the RAII guards -/// provided as closure arguments from [`lock_with`] and [`try_lock_with`], which -/// guarantees that the data is only ever accessed when the mutex is locked. -/// -/// # Examples -/// -/// ``` -/// use std::sync::Arc; -/// use std::thread; -/// use std::sync::mpsc::channel; -/// -/// use mcslock::thread_local::Mutex; -/// use mcslock::relax::Spin; -/// -/// type SpinMutex = Mutex; -/// -/// const N: usize = 10; -/// -/// // Spawn a few threads to increment a shared variable (non-atomically), and -/// // let the main thread know once all increments are done. -/// // -/// // Here we're using an Arc to share memory among threads, and the data inside -/// // the Arc is protected with a mutex. -/// let data = Arc::new(SpinMutex::new(0)); -/// -/// let (tx, rx) = channel(); -/// for _ in 0..N { -/// let (data, tx) = (data.clone(), tx.clone()); -/// thread::spawn(move || { -/// // The shared state can only be accessed once the lock is held. -/// // Our non-atomic increment is safe because we're the only thread -/// // which can access the shared state when the lock is held. -/// // -/// // We unwrap() the return value to assert that we are not expecting -/// // threads to ever fail while holding the lock. -/// // -/// // Data is exclusively accessed by the guard argument. -/// data.lock_with(|mut data| { -/// *data += 1; -/// if *data == N { -/// tx.send(()).unwrap(); -/// } -/// // the lock is unlocked here when `data` goes out of scope. -/// }) -/// }); -/// } -/// -/// rx.recv().unwrap(); -/// ``` -/// [`new`]: Mutex::new -/// [`lock_with`]: Mutex::lock_with -/// [`try_lock_with`]: Mutex::try_lock_with -pub struct Mutex(RawMutex); - -// Same unsafe impls as `crate::raw::Mutex`. -unsafe impl Send for Mutex {} -unsafe impl Sync for Mutex {} - -impl Mutex { - /// Creates a new mutex in an unlocked state ready for use. - /// - /// # Examples - /// - /// ``` - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// const MUTEX: SpinMutex = SpinMutex::new(0); - /// let mutex = SpinMutex::new(0); - /// ``` - #[cfg(not(all(loom, test)))] - #[inline] - pub const fn new(value: T) -> Self { - Self(RawMutex::new(value)) - } - - /// Creates a new unlocked mutex with Loom primitives (non-const). - #[cfg(all(loom, test))] - #[cfg(not(tarpaulin_include))] - fn new(value: T) -> Self { - Self(RawMutex::new(value)) - } - - /// Consumes this mutex, returning the underlying data. - /// - /// # Examples - /// - /// ``` - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = SpinMutex::new(0); - /// assert_eq!(mutex.into_inner(), 0); - /// ``` - #[inline(always)] - pub fn into_inner(self) -> T { - self.0.into_inner() - } -} - -impl Mutex { - /// Attempts to acquire this mutex and then runs a closure against its guard. - /// - /// If the lock could not be acquired at this time, then a [`None`] value is - /// given back as the closure argument. If the lock has been acquired, then - /// a [`Some`] value with the mutex guard is given instead. The lock will be - /// unlocked when the guard is dropped. - /// - /// This function does not block. - /// - /// # Panics - /// - /// At most one [`thread_local::Mutex`] may be held within a single thread at - /// any time. Trying to acquire a second lock while a guard is still alive - /// will cause a panic. - /// - /// [`thread_local::Mutex`]: Mutex - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// use std::thread; - /// - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = Arc::new(SpinMutex::new(0)); - /// let c_mutex = Arc::clone(&mutex); - /// - /// thread::spawn(move || { - /// c_mutex.try_lock_with(|guard| { - /// if let Some(mut guard) = guard { - /// *guard = 10; - /// } else { - /// println!("try_lock failed"); - /// } - /// }); - /// }) - /// .join().expect("thread::spawn failed"); - /// - /// assert_eq!(mutex.lock_with(|guard| *guard), 10); - /// ``` - /// - /// Borrows of the guard or its data cannot escape the given closure. - /// - /// ```compile_fail,E0515 - /// use mcslock::thread_local::spins::Mutex; - /// - /// let mutex = Mutex::new(1); - /// let data = mutex.try_lock_with(|guard| &*guard.unwrap()); - /// ``` - /// - /// An example of panic: - /// - #[doc = concat!("```should_panic,", literal_panic_msg!())] - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = SpinMutex::new(0); - /// - /// mutex.lock_with(|_guard| { - /// let mutex = SpinMutex::new(()); - /// // Acquiring more than one thread_local::Mutex within a single - /// // thread at the same time is not allowed, this will panic. - /// mutex.try_lock_with(|_guard| ()); - /// }); - /// ``` - #[inline] - #[track_caller] - pub fn try_lock_with(&self, f: F) -> Ret - where - F: FnOnce(Option>) -> Ret, - { - self.with_borrow_mut(|raw, node| f(MutexGuard::try_new(raw, node))) - } - - /// Attempts to acquire this mutex and then runs a closure against its guard. - /// - /// If the lock could not be acquired at this time, then a [`None`] value is - /// given back as the closure argument. If the lock has been acquired, then - /// a [`Some`] value with the mutex guard is given instead. The lock will be - /// unlocked when the guard is dropped. - /// - /// This function does not block, and also does not check if the thread local - /// node is already in use. - /// - /// # Safety - /// - /// At most one [`thread_local::Mutex`] may be held within a single thread at - /// any time. Trying to acquire a second lock while a guard is still alive - /// is undefined behavior. - /// - /// [`thread_local::Mutex`]: Mutex - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// use std::thread; - /// - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = Arc::new(SpinMutex::new(0)); - /// let c_mutex = Arc::clone(&mutex); - /// - /// thread::spawn(move || unsafe { - /// c_mutex.try_lock_with_unchecked(|guard| { - /// if let Some(mut guard) = guard { - /// *guard = 10; - /// } else { - /// println!("try_lock failed"); - /// } - /// }); - /// }) - /// .join().expect("thread::spawn failed"); - /// - /// assert_eq!(mutex.lock_with(|guard| *guard), 10); - /// ``` - /// - /// Borrows of the guard or its data cannot escape the given closure. - /// - /// ```compile_fail,E0515 - /// use mcslock::thread_local::spins::Mutex; - /// - /// let mutex = Mutex::new(1); - /// let data = unsafe { mutex.try_lock_with_unchecked(|g| &*g.unwrap()) }; - /// ``` - /// - /// An example of undefined behavior: - /// - /// ```no_run - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = SpinMutex::new(0); - /// - /// mutex.lock_with(|_guard| unsafe { - /// let mutex = SpinMutex::new(()); - /// // Acquiring more than one thread_local::Mutex within a single - /// // thread at the same time is not allowed, this is UB. - /// mutex.try_lock_with_unchecked(|_guard| ()); - /// }); - /// ``` - #[inline] - pub unsafe fn try_lock_with_unchecked(&self, f: F) -> Ret - where - F: FnOnce(Option>) -> Ret, - { - self.with_borrow_mut_unchecked(|raw, node| f(MutexGuard::try_new(raw, node))) - } - - /// Acquires this mutex and then runs the closure against its guard. - /// - /// This function will block the local thread until it is available to acquire - /// the mutex. Upon acquiring the mutex, the user provided closure will be - /// executed against the mutex guard. Once the guard goes out of scope, it - /// will unlock the mutex. - /// - /// This function will block if the lock is unavailable. - /// - /// # Panics - /// - /// At most one [`thread_local::Mutex`] may be held within a single thread at - /// any time. Trying to acquire a second lock while a guard is still alive - /// will cause a panic. - /// - /// [`thread_local::Mutex`]: Mutex - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// use std::thread; - /// - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = Arc::new(SpinMutex::new(0)); - /// let c_mutex = Arc::clone(&mutex); - /// - /// thread::spawn(move || { - /// c_mutex.lock_with(|mut guard| *guard = 10); - /// }) - /// .join().expect("thread::spawn failed"); - /// - /// assert_eq!(mutex.lock_with(|guard| *guard), 10); - /// ``` - /// - /// Borrows of the guard or its data cannot escape the given closure. - /// - /// ```compile_fail,E0515 - /// use mcslock::thread_local::spins::Mutex; - /// - /// let mutex = Mutex::new(1); - /// let data = mutex.lock_with(|guard| &*guard); - /// ``` - /// - /// An example of panic: - /// - #[doc = concat!("```should_panic,", literal_panic_msg!())] - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = SpinMutex::new(0); - /// - /// mutex.lock_with(|_guard| { - /// let mutex = SpinMutex::new(()); - /// // Acquiring more than one thread_local::Mutex within a single - /// // thread at the same time is not allowed, this will panic. - /// mutex.lock_with(|_guard| ()); - /// }); - /// ``` - #[inline] - #[track_caller] - pub fn lock_with(&self, f: F) -> Ret - where - F: FnOnce(MutexGuard<'_, T, R>) -> Ret, - { - self.with_borrow_mut(|raw, node| f(MutexGuard::new(raw, node))) - } - - /// Acquires this mutex and then runs the closure against its guard. - /// - /// This function will block the local thread until it is available to acquire - /// the mutex. Upon acquiring the mutex, the user provided closure will be - /// executed against the mutex guard. Once the guard goes out of scope, it - /// will unlock the mutex. - /// - /// This function will block if the lock is unavailable, and also does not - /// check if the thread local node is already in use. - /// - /// # Safety - /// - /// At most one [`thread_local::Mutex`] may be held within a single thread at - /// any time. Trying to acquire a second lock while a guard is still alive - /// is undefined behavior. - /// - /// [`thread_local::Mutex`]: Mutex - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// use std::thread; - /// - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = Arc::new(SpinMutex::new(0)); - /// let c_mutex = Arc::clone(&mutex); - /// - /// thread::spawn(move || { - /// unsafe { c_mutex.lock_with_unchecked(|mut g| *g = 10) }; - /// }) - /// .join().expect("thread::spawn failed"); - /// - /// assert_eq!(mutex.lock_with(|guard| *guard), 10); - /// ``` - /// - /// Borrows of the guard or its data cannot escape the given closure. - /// - /// ```compile_fail,E0515 - /// use mcslock::thread_local::spins::Mutex; - /// - /// let mutex = Mutex::new(1); - /// let data = unsafe { mutex.lock_with_unchecked(|g| &*g) }; - /// ``` - /// - /// An example of undefined behavior: - /// - /// ```no_run - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = SpinMutex::new(0); - /// - /// mutex.lock_with(|_guard| unsafe { - /// let mutex = SpinMutex::new(()); - /// // Acquiring more than one thread_local::Mutex within a single - /// // thread at the same time is not allowed, this is UB. - /// mutex.lock_with_unchecked(|_guard| ()); - /// }); - /// ``` - #[inline] - pub unsafe fn lock_with_unchecked(&self, f: F) -> Ret - where - F: FnOnce(MutexGuard<'_, T, R>) -> Ret, - { - self.with_borrow_mut_unchecked(|raw, node| f(MutexGuard::new(raw, node))) - } -} - -impl Mutex { - /// Returns `true` if the lock is currently held. - /// - /// This method does not provide any synchronization guarantees, so its only - /// useful as a heuristic, and so must be considered not up to date. - /// - /// # Example - /// - /// ``` - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mutex = SpinMutex::new(0); - /// - /// mutex.lock_with(|mut guard| *guard = 1); - /// - /// assert_eq!(mutex.is_locked(), false); - /// ``` - #[inline] - pub fn is_locked(&self) -> bool { - self.0.is_locked() - } - - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the `Mutex` mutably, no actual locking needs to - /// take place - the mutable borrow statically guarantees no locks exist. - /// - /// # Examples - /// - /// ``` - /// use mcslock::thread_local::Mutex; - /// use mcslock::relax::Spin; - /// - /// type SpinMutex = Mutex; - /// - /// let mut mutex = SpinMutex::new(0); - /// *mutex.get_mut() = 10; - /// - /// assert_eq!(mutex.lock_with(|guard| *guard), 10); - /// ``` - #[cfg(not(all(loom, test)))] - #[inline(always)] - pub fn get_mut(&mut self) -> &mut T { - self.0.get_mut() - } - - /// Runs `f` over a raw mutex and thread local node as arguments. - /// - /// # Panics - /// - /// Will panic if the thread local [`MutexNode`] is already in use by a - /// lock guard from the same thread. - #[track_caller] - fn with_borrow_mut(&self, f: F) -> Ret - where - F: FnOnce(&RawMutex, &mut MutexNode) -> Ret, - { - let caller = Location::caller(); - let panic = |_| panic_already_held(caller); - let f = |mut node: RefMut<_>| f(&self.0, &mut node); - NODE.with(|node| node.try_borrow_mut().map_or_else(panic, f)) - } - - /// Runs 'f' over the a raw mutex and thread local node as arguments without - /// checking if the node is currently mutably borrowed. - /// - /// # Safety - /// - /// Mutably borrowing a [`RefCell`] while references are still live is - /// undefined behaviour. This means that two or more guards can not be in - /// scope within a single thread at the same time. - unsafe fn with_borrow_mut_unchecked(&self, f: F) -> Ret - where - F: FnOnce(&RawMutex, &mut MutexNode) -> Ret, - { - // SAFETY: Caller guaranteed that no other references are live. - NODE.with(|node| f(&self.0, unsafe { &mut *node.as_ptr() })) - } -} - -impl Default for Mutex { - /// Creates a `Mutex` with the `Default` value for `T`. - #[inline] - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl From for Mutex { - /// Creates a `Mutex` from a instance of `T`. - #[inline] - fn from(data: T) -> Self { - Self::new(data) - } -} - -impl fmt::Debug for Mutex { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("Mutex"); - self.try_lock_with(|guard| match guard { - Some(guard) => guard.inner.with(|data| d.field("data", &data)), - None => d.field("data", &format_args!("")), - }); - d.finish() - } -} - -#[cfg(test)] -impl LockNew for Mutex { - type Target = T; - - fn new(value: Self::Target) -> Self - where - Self::Target: Sized, - { - Self::new(value) - } -} - -#[cfg(test)] -impl LockWith for Mutex { - type Guard<'a> = MutexGuard<'a, Self::Target, R> - where - Self: 'a, - Self::Target: 'a; - - fn try_lock_with(&self, f: F) -> Ret - where - F: FnOnce(Option>) -> Ret, - { - self.try_lock_with(f) - } - - fn lock_with(&self, f: F) -> Ret - where - F: FnOnce(MutexGuard<'_, T, R>) -> Ret, - { - self.lock_with(f) - } - - fn is_locked(&self) -> bool { - self.is_locked() - } -} - -#[cfg(all(not(loom), test))] -impl crate::test::LockData for Mutex { - fn into_inner(self) -> Self::Target - where - Self::Target: Sized, - { - self.into_inner() - } - - fn get_mut(&mut self) -> &mut Self::Target { - self.get_mut() - } -} - -/// A type that calls the unchecked versions of `lock_with` and `try_lock_with` -/// when implementing the `LockWith` trait. -#[cfg(test)] -struct MutexUnchecked(Mutex); - -#[cfg(test)] -impl LockNew for MutexUnchecked { - type Target = T; - - fn new(value: Self::Target) -> Self - where - Self::Target: Sized, - { - Self(Mutex::new(value)) - } -} - -/// Implements the test locking interface to verify the unchecked APIs: -/// `lock_with_unchecked` and `try_lock_with_unchecked`. -/// -/// # Safety -/// -/// Callers must guarantee that no more than ONE mutex guard is alive at any -/// time within a single thread. When failling to do so under Miri testing, Miri -/// has flagged the test as invoking UB, which is nice. Loom execution does not -/// necessarily catch such problems, although those can inadvertently break the -/// memory model invariants which will then fire Loom errors. -#[cfg(test)] -impl LockWith for MutexUnchecked { - type Guard<'a> = MutexGuard<'a, Self::Target, R> - where - Self: 'a, - Self::Target: 'a; - - fn try_lock_with(&self, f: F) -> Ret - where - F: FnOnce(Option>) -> Ret, - { - unsafe { self.0.try_lock_with_unchecked(f) } - } - - fn lock_with(&self, f: F) -> Ret - where - F: FnOnce(MutexGuard<'_, T, R>) -> Ret, - { - unsafe { self.0.lock_with_unchecked(f) } - } - - fn is_locked(&self) -> bool { - self.0.is_locked() - } -} - -/// An RAII implementation of a "scoped lock" of a mutex. When this structure is -/// dropped (falls out of scope), the lock will be unlocked. -/// -/// The data protected by the mutex can be access through this guard via its -/// [`Deref`] and [`DerefMut`] implementations. -/// -/// This structure is given as closure argument by [`lock_with`] and -/// [`try_lock_with`] methods on [`Mutex`]. -/// -/// [`Deref`]: core::ops::Deref -/// [`DerefMut`]: core::ops::DerefMut -/// [`lock_with`]: Mutex::lock_with -/// [`try_lock_with`]: Mutex::try_lock_with -#[must_use = "if unused the Mutex will immediately unlock"] -pub struct MutexGuard<'a, T: ?Sized, R: Relax> { - inner: RawMutexGuard<'a, T, R>, - // Guard will access thread local storage during drop call, can't be Send. - marker: PhantomData<*mut ()>, -} - -// SAFETY: Guard only access thread local storage during drop call, can be Sync. -unsafe impl Sync for MutexGuard<'_, T, R> {} - -impl<'a, T: ?Sized, R: Relax> MutexGuard<'a, T, R> { - /// Creates a new guard instance from a raw guard. - fn raw(inner: RawMutexGuard<'a, T, R>) -> Self { - Self { inner, marker: PhantomData } - } - - /// Creates a new guard instance only if the mutex is not already locked. - fn try_new(mutex: &'a RawMutex, node: &'a mut MutexNode) -> Option { - mutex.try_lock(node).map(Self::raw) - } - - /// Creates a new guard instance by locking a raw mutex. - fn new(mutex: &'a RawMutex, node: &'a mut MutexNode) -> Self { - Self::raw(mutex.lock(node)) - } -} - -impl<'a, T: ?Sized + Debug, R: Relax> Debug for MutexGuard<'a, T, R> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl<'a, T: ?Sized + Display, R: Relax> Display for MutexGuard<'a, T, R> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -#[cfg(not(all(loom, test)))] -impl<'a, T: ?Sized, R: Relax> core::ops::Deref for MutexGuard<'a, T, R> { - type Target = T; - - /// Dereferences the guard to access the underlying data. - #[inline(always)] - fn deref(&self) -> &T { - &self.inner - } -} - -#[cfg(not(all(loom, test)))] -impl<'a, T: ?Sized, R: Relax> core::ops::DerefMut for MutexGuard<'a, T, R> { - /// Mutably dereferences the guard to access the underlying data. - #[inline(always)] - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -/// SAFETY: A guard instance hold the lock locked, with exclusive access to the -/// underlying data. -#[cfg(all(loom, test))] -#[cfg(not(tarpaulin_include))] -unsafe impl crate::loom::Guard for MutexGuard<'_, T, R> { - type Target = T; - - fn get(&self) -> &loom::cell::UnsafeCell { - self.inner.get() - } -} - -#[cfg(all(not(loom), test))] -mod test { - use crate::relax::Yield; - use crate::test::tests; - use crate::thread_local::yields::Mutex; - - type MutexUnchecked = super::MutexUnchecked; - - #[test] - fn lots_and_lots() { - tests::lots_and_lots::>(); - } - - #[test] - fn lots_and_lots_unchecked() { - tests::lots_and_lots::>(); - } - - #[test] - fn smoke() { - tests::smoke::>(); - } - - #[test] - fn smoke_unchecked() { - tests::smoke::>(); - } - - #[test] - fn test_guard_debug_display() { - tests::test_guard_debug_display::>(); - } - - #[test] - fn test_mutex_debug() { - tests::test_mutex_debug::>(); - } - - #[test] - fn test_mutex_from() { - tests::test_mutex_from::>(); - } - - #[test] - fn test_mutex_default() { - tests::test_mutex_default::>(); - } - - #[test] - fn test_try_lock() { - tests::test_try_lock::>(); - } - - #[test] - fn test_try_lock_unchecked() { - tests::test_try_lock::>(); - } - - #[test] - fn test_into_inner() { - tests::test_into_inner::>(); - } - - #[test] - fn test_into_inner_drop() { - tests::test_into_inner_drop::>(); - } - - #[test] - fn test_get_mut() { - tests::test_get_mut::>(); - } - - #[test] - #[should_panic = literal_panic_msg!()] - fn test_lock_arc_nested() { - tests::test_lock_arc_nested::, Mutex<_>>(); - } - - #[test] - #[should_panic = literal_panic_msg!()] - fn test_acquire_more_than_one_lock() { - tests::test_acquire_more_than_one_lock::>(); - } - - #[test] - fn test_lock_arc_access_in_unwind() { - tests::test_lock_arc_access_in_unwind::>(); - } - - #[test] - fn test_lock_arc_access_in_unwind_uncheckd() { - tests::test_lock_arc_access_in_unwind::>(); - } - - #[test] - fn test_lock_unsized() { - tests::test_lock_unsized::>(); - } - - #[test] - fn test_lock_unsized_unchecked() { - tests::test_lock_unsized::>(); - } -} - -#[cfg(all(loom, test))] -mod model { - use super::MutexUnchecked as InnerUnchecked; - - use crate::loom::models; - use crate::relax::Yield; - use crate::thread_local::yields::Mutex; - - type MutexUnchecked = InnerUnchecked; - - #[test] - fn try_lock_join() { - models::try_lock_join::>(); - } - - #[test] - fn try_lock_join_unchecked() { - models::try_lock_join::>(); - } - - #[test] - fn lock_join() { - models::lock_join::>(); - } - - #[test] - fn lock_join_unchecked() { - models::lock_join::>(); - } - - #[test] - fn mixed_lock_join() { - models::mixed_lock_join::>(); - } - - #[test] - fn mixed_lock_join_unchecked() { - models::mixed_lock_join::>(); - } -}