maitake_sync/blocking.rs
1//! Synchronous (blocking) synchronization primitives.
2//!
3//! The core synchronization primitives in `maitake-sync`, such as
4//! [`Mutex`](crate::Mutex), [`RwLock`](crate::RwLock), and
5//! [`WaitQueue`](crate::WaitQueue) are _asynchronous_. They are designed to be
6//! used with [`core::task`] and [`core::future`], and when it is necessary to
7//! wait for another task to complete some work for the current task to proceed,
8//! `maitake`'s synchronization primitives wait by *yielding* to the
9//! asynchronous task scheduler to allow another task to proceed.
10//!
11//! This module, on the other hand, provides _synchronous_ (or _blocking_)
12//! synchronization primitives. Rather than yielding to the runtime, these
13//! synchronization primitives will block the current CPU core (or thread, if
14//! running in an environment with threads) until they are woken by other cores.
15//! These synchronization primitives are, in some cases, necessary to implement
16//! the async synchronization primitives that form `maitake-sync`'s core APIs.
17//! They are also exposed publicly in this module so that they can be used in
18//! other projects when a blocking-based synchronization primitive is needed.
19//!
20//! This module provides the following synchronization primitive types:
21//!
22//! - [`Mutex`]: a synchronous [mutual exclusion lock].
23//! - [`RwLock`]: a synchronous [reader-writer lock].
24//!
25//! # overriding mutex implementations
26//!
27//! By default, the [`Mutex`] type uses a [`DefaultMutex`] as the underlying
28//! blocking strategy. This type attempts to choose a suitable implementation
29//! for the blocking mutex based on the currently available [feature flags]. When
30//! the `std` feature is not enabled, this is typically a _[spinlock]_, which
31//! waits for the lock to become available by _spinning_: repeatedly checking an
32//! atomic value in a loop, executing [spin-loop hint instructions] until the
33//! lock value changes. These spinlock implementations are represented by the
34//! [`Spinlock`] and [`RwSpinlock`] types in the [`spin`](crate::spin) module.
35//!
36//! Spinlocks are simple to implement and, thanks to the Rust standard library
37//! abstracting over atomic operations, portable. The default spinlock will work
38//! on any platform with support for atomic compare-and-swap operations.[^1]
39//! However, there are a number of reasons why a generic spinlock may not be
40//! desirable for all use-cases. For example:
41//!
42//! - On single-core platforms, there *is* no concurrent thread of execution
43//! which can acquire and release a lock. If code running on a single-core
44//! system attempts to acquire a lock and finds that it is already locked,
45//! waiting for the lock to be released will never work, since the *same*
46//! thread of execution is holding the lock already. On such systems, any
47//! attempt to lock a mutex that is locked is guaranteed to be a deadlock.
48//! Therefore, code which can guarantee that it will only run on single-core
49//! CPUs may prefer to avoid the complexity of a "real" lock implementation
50//! altogether, and panic (or otherwise alert the programmer) rather than
51//! deadlocking when attempting to acquire a mutex that is already locked.
52//! - In bare-metal code, the data protected by a mutex may be shared not only
53//! with concurrent threads of execution, but with [interrupt handlers] as
54//! well. When this is the case, it may be necessary for acquiring a lock to
55//! also disable interrupts while the lock is held (and re-enable then when
56//! the lock is released) to avoid racing with code that runs in an interrupt
57//! handler.
58//! - While spinlocks are simple and portable, they are [not the most
59//! *efficient*][busy-wait]. A CPU core waiting on a spinlock draws power
60//! while in a spin loop, and is not executing any other tasks. Systems with a
61//! notion of a scheduler, whether provided by an operating system or
62//! implemented directly within a bare-metal program, may prefer to yield to
63//! the scheduler when waiting for a lock.
64//! - Some hardware platforms provide mechanisms to optimize the performance of
65//! spinlocks, such as [Hardware Lock Elision] on x86 CPUs. When such features
66//! are available, using them can improve performance and efficiency.
67//!
68//! However, all of these alternative waiting strategies knowledge of either the
69//! underlying hardware platform, the specific details of the system, or both. A
70//! single-core "fake" spinlock naturally requires the knowledge that the
71//! hardware platform is single-core, and hardware lock elision similarly
72//! requires knowledge about platform-specific features. Similarly, disabling
73//! interrupts may require application-specific as well as hardware-specific
74//! code, especially if only certain interrupts need to be disabled. And, of
75//! course, yielding to a scheduler requires scheduler-specific code. Since
76//! `maitake-sync` is a platform-agnostic, generic library, it does not provide
77//! its own implementations of such behaviors. Instead, users which need a
78//! blocking strategy other than the default spinlock may *override* the default
79//! behavior using the traits provided by the [`mutex-traits`] crate.
80//!
81//! The [`Mutex`] type in this module is generic over an additional `Lock` type
82//! parameter, which represents the actual raw mutex implementation. This type
83//! parameter defaults to the [`Spinlock`] type, but it can be overridden to an
84//! arbitrary user-provided type. The [`mutex-traits`] crate provides two
85//! traits representing a raw mutex, [`mutex_traits::ScopedRawMutex`] and
86//! [`mutex_traits::RawMutex`]. These can be implemented to provide a custom
87//! lock implementation. [`mutex_traits::RawMutex`] represents a generic
88//! mutual-exclusion lock that can be freely locked and unlocked at any time,
89//! while [`mutex_traits::ScopedRawMutex`] is a subset of [`RawMutex`] for which
90//! locks can only be acquired and released for the duration of a closure. As
91//! the functionality of [`RawMutex`] is a superset of [`ScopedRawMutex`], all
92//! types which implement [`RawMutex`] also implement [`ScopedRawMutex`]. In
93//! general, it is recommended for user lock types implement the more flexible
94//! [`RawMutex`] trait rather than [`ScopedRawMutex`], if possible:
95//! [`ScopedRawMutex`] exists to support more restricted lock types which
96//! require scoped lock-and-unlock operations. Finally, the [`ConstInit`] trait
97//! abstracts over `const fn` initialization of a raw mutex type, and is
98//! required for `const fn` constructors with a custom raw mutex type.
99//!
100//! When the `Lock` type parameter implements [`ScopedRawMutex`], the [`Mutex`]
101//! type provides the [`Mutex::with_lock`] and [`Mutex::try_with_lock`] methods,
102//! which execute a closure with the mutex locked, and release the lock when the
103//! closure returns. When the `Lock` type parameter also implements
104//! [`RawMutex`], the [`Mutex`] type provides [`Mutex::lock`] and
105//! [`Mutex::try_lock`] methods, which return a RAII [`MutexGuard`], similar to
106//! the interface provided by [`std::sync::Mutex`]. The [`Mutex::new`] function
107//! returns a [`Mutex`] using the default spinlock. To instead construct a
108//! [`Mutex`] with a custom [`RawMutex`] implementation, use the
109//! [`Mutex::new_with_raw_mutex`] function.
110//!
111//! Furthermore, many *async* synchronization primitives provided by this crate,
112//! such as the [async `Mutex`](crate::Mutex), [async `RwLock`], [`WaitQueue`],
113//! [`WaitMap`], and [`Semaphore`], internally depend on the blocking `Mutex`
114//! for wait list synchronization. These types are *also* generic over a `Lock`
115//! type parameter, and also provide `new_with_raw_mutex` constructors, such as
116//! [`WaitQueue::new_with_raw_mutex`](crate::WaitQueue::new_with_raw_mutex). This allows
117//! the blocking mutex used by these types to be overridden. The majority
118//! `maitake-sync`'s async synchronization types only require the `Lock` type to
119//! implement [`ScopedRawMutex`]. However, the [`Semaphore`] and [async
120//! `RwLock`] require the more permissive [`RawMutex`] trait.
121//!
122//! The [`mutex` crate] provides a number of types implementing [`RawMutex`] and
123//! [`ScopedRawMutex`], including adapters for compatibility with the
124//! [`lock_api`] and [`critical-section`] crates.
125//!
126//! Similarly to the [`RawMutex`] trait, the blocking [`RwLock`] type in this
127//! module is generic over a `Lock` type parameter, which must implement the
128//! [`RawRwLock`] trait. This allows the `RwLock`'s blocking behavior to be
129//! overridden similarly to [`Mutex`].
130//!
131//! [^1]: Including those where "atomics" are implemented by the
132//! `portable-atomic` crate, as described
133//! [here](crate#support-for-atomic-operations).
134//!
135//! [mutual exclusion lock]: https://en.wikipedia.org/wiki/Mutual_exclusion
136//! [reader-writer lock]:
137//! https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
138//! [spinlock]: https://en.wikipedia.org/wiki/Spinlock
139//! [spin-loop hint instructions]: core::hint::spin_loop
140//! [`Spinlock`]: crate::spin::Spinlock
141//! [`RwSpinlock`]: crate::spin::RwSpinlock
142//! [interrupt handler]: https://en.wikipedia.org/wiki/Interrupt_handler
143//! [busy-wait]: https://en.wikipedia.org/wiki/Spinlock#Alternatives
144//! [Hardware Lock Elision]:
145//! https://en.wikipedia.org/wiki/Transactional_Synchronization_Extensions#HLE
146//! [`WaitQueue`]: crate::WaitQueue
147//! [`WaitMap`]: crate::WaitMap
148//! [`Semaphore`]: crate::Semaphore
149//! [async `RwLock`]: crate::RwLock
150//! [`mutex-traits`]: https://docs.rs/mutex-traits
151//! [`mutex` crate]: https://crates.io/crates/mutex
152//! [`lock_api`]: https://crates.io/crates/lock_api
153//! [`critical-section`]: https://crates.io/crates/critical-section
154//! [`std::sync::Mutex`]:
155//! https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html
156//! [feature flags]: crate#features
157mod default_mutex;
158pub(crate) mod mutex;
159pub(crate) mod rwlock;
160
161pub use self::{mutex::*, rwlock::*};
162
163pub use default_mutex::DefaultMutex;
164
165pub use mutex_traits::ConstInit;