diff options
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | examples/basic.rs | 4 | ||||
| -rw-r--r-- | examples/dining_philosophers.rs | 2 | ||||
| -rw-r--r-- | examples/double_mutex.rs | 4 | ||||
| -rw-r--r-- | examples/list.rs | 4 | ||||
| -rw-r--r-- | src/collection.rs | 4 | ||||
| -rw-r--r-- | src/collection/collection.rs | 132 | ||||
| -rw-r--r-- | src/key.rs | 20 | ||||
| -rw-r--r-- | src/mutex.rs | 2 | ||||
| -rw-r--r-- | src/mutex/guard.rs | 4 | ||||
| -rw-r--r-- | src/mutex/mutex.rs | 20 | ||||
| -rw-r--r-- | src/rwlock.rs | 57 | ||||
| -rw-r--r-- | src/rwlock/read_guard.rs | 2 | ||||
| -rw-r--r-- | src/rwlock/read_lock.rs | 40 | ||||
| -rw-r--r-- | src/rwlock/rwlock.rs | 166 | ||||
| -rw-r--r-- | src/rwlock/write_guard.rs | 4 | ||||
| -rw-r--r-- | src/rwlock/write_lock.rs | 19 |
17 files changed, 428 insertions, 68 deletions
@@ -24,7 +24,7 @@ let data: Mutex<i32> = Mutex::new(0); for _ in 0..N { thread::spawn(move || { // each thread gets one thread key - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); // unlocking a mutex requires a ThreadKey let mut data = data.lock(key); @@ -34,7 +34,7 @@ for _ in 0..N { }); } -let key = ThreadKey::lock().unwrap(); +let key = ThreadKey::get().unwrap(); let data = data.lock(&mut key); println!("{}", *data); ``` @@ -49,7 +49,7 @@ static DATA_2: Mutex<String> = Mutex::new(String::new()); for _ in 0..N { thread::spawn(move || { - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); // happylock ensures at runtime there are no duplicate locks let collection = LockCollection::try_new((&DATA_1, &DATA_2)).unwrap(); @@ -60,7 +60,7 @@ for _ in 0..N { }); } -let key = ThreadKey::lock().unwrap(); +let key = ThreadKey::get().unwrap(); let data = (&DATA_1, &DATA_2); let data = LockGuard::lock(&data, &mut key); println!("{}", *data.0); @@ -69,7 +69,7 @@ println!("{}", *data.1); ## Performance -**The `ThreadKey` is a mostly-zero cost abstraction.** It doesn't use any memory, and it doesn't really exist at run-time. The only cost comes from calling `ThreadKey::lock()`, because the function has to ensure at runtime that the key hasn't already been taken. Dropping the key will also have a small cost. +**The `ThreadKey` is a mostly-zero cost abstraction.** It doesn't use any memory, and it doesn't really exist at run-time. The only cost comes from calling `ThreadKey::get()`, because the function has to ensure at runtime that the key hasn't already been taken. Dropping the key will also have a small cost. **Avoid `LockCollection::try_new`.** This constructor will check to make sure that the collection contains no duplicate locks. This is an O(n^2) operation, where n is the number of locks in the collection. `LockCollection::new` and `LockCollection::new_ref` don't need these checks because they use `OwnedLockable`, which is guaranteed to be unique as long as it is accessible. As a last resort, `LockCollection::new_unchecked` doesn't do this check, but is unsafe to call. @@ -79,6 +79,8 @@ println!("{}", *data.1); Although this library is able to successfully prevent deadlocks, livelocks may still be an issue. Imagine thread 1 gets resource 1, thread 2 gets resource 2, thread 1 realizes it can't get resource 2, thread 2 realizes it can't get resource 1, thread 1 drops resource 1, thread 2 drops resource 2, and then repeat forever. In practice, this situation probably wouldn't last forever. But it would be nice if this could be prevented somehow. A more fair system for getting sets of locks would help, but I have no clue what that looks like. +It might to possible to break the `ThreadKey` system by having two crates import this crate and call `ThreadKey::get`. I'm not quite sure how this works, but Rust could decide to give each crate their own key, ergo one thread would get two keys. I don't think the standard library would have this issue. At a certain point, I have to recognize that someone could also just import the standard library mutex and get a deadlock that way. + We should add `Condvar` at some point. I didn't because I've never used it before, and I'm probably not the right person to solve this problem. I think all the synchronization problems could be solved by having `Condvar::wait` take a `ThreadKey` instead of a `MutexGuard`. Something similar can probably be done for `Barrier`. But again, I'm no expert. Do `OnceLock` or `LazyLock` ever deadlock? We might not need to add those here. diff --git a/examples/basic.rs b/examples/basic.rs index 8842e16..1d611d3 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -10,7 +10,7 @@ fn main() { let mut threads = Vec::new(); for _ in 0..N { let th = thread::spawn(move || { - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); let mut data = DATA.lock(key); *data += 1; }); @@ -21,7 +21,7 @@ fn main() { _ = th.join(); } - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); let data = DATA.lock(key); println!("{}", *data); } diff --git a/examples/dining_philosophers.rs b/examples/dining_philosophers.rs index f8657df..35aa330 100644 --- a/examples/dining_philosophers.rs +++ b/examples/dining_philosophers.rs @@ -46,7 +46,7 @@ struct Philosopher { impl Philosopher { fn cycle(&self) { - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); thread::sleep(Duration::from_secs(1)); // safety: no philosopher asks for the same fork twice diff --git a/examples/double_mutex.rs b/examples/double_mutex.rs index 320f461..621f81e 100644 --- a/examples/double_mutex.rs +++ b/examples/double_mutex.rs @@ -10,7 +10,7 @@ fn main() { let mut threads = Vec::new(); for _ in 0..N { let th = thread::spawn(move || { - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); let lock = LockCollection::new_ref(&DATA); let mut guard = lock.lock(key); *guard.1 = (100 - *guard.0).to_string(); @@ -23,7 +23,7 @@ fn main() { _ = th.join(); } - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); let data = LockCollection::new_ref(&DATA); let data = data.lock(key); println!("{}", *data.0); diff --git a/examples/list.rs b/examples/list.rs index 14117ee..3b03f2d 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -29,7 +29,7 @@ fn main() { let mut threads = Vec::new(); for _ in 0..N { let th = thread::spawn(move || { - let mut key = ThreadKey::lock().unwrap(); + let mut key = ThreadKey::get().unwrap(); loop { let mut data = Vec::new(); for _ in 0..3 { @@ -55,7 +55,7 @@ fn main() { _ = th.join(); } - let key = ThreadKey::lock().unwrap(); + let key = ThreadKey::get().unwrap(); let data = LockCollection::new_ref(&DATA); let data = data.lock(key); for val in &*data { diff --git a/src/collection.rs b/src/collection.rs index 1253a0f..c6cbe2d 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -12,10 +12,10 @@ mod guard; /// important that no duplicate locks are included within. #[derive(Debug, Clone, Copy)] pub struct LockCollection<L> { - collection: L, + data: L, } -/// A guard for a generic [`Lockable`] type. +/// A RAII guard for a generic [`Lockable`] type. pub struct LockGuard<'a, 'key: 'a, L: Lockable<'a>, Key: Keyable + 'key> { guard: L::Output, key: Key, diff --git a/src/collection/collection.rs b/src/collection/collection.rs index eb3bb69..cc8b334 100644 --- a/src/collection/collection.rs +++ b/src/collection/collection.rs @@ -26,13 +26,13 @@ impl<'a, L: OwnedLockable<'a>> From<L> for LockCollection<L> { impl<'a, L: OwnedLockable<'a>> AsRef<L> for LockCollection<L> { fn as_ref(&self) -> &L { - &self.collection + &self.data } } impl<'a, L: OwnedLockable<'a>> AsMut<L> for LockCollection<L> { fn as_mut(&mut self) -> &mut L { - &mut self.collection + &mut self.data } } @@ -53,7 +53,7 @@ impl<L: IntoIterator> IntoIterator for LockCollection<L> { type IntoIter = L::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.collection.into_iter() + self.data.into_iter() } } @@ -65,7 +65,7 @@ where type IntoIter = <&'a L as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.collection.into_iter() + self.data.into_iter() } } @@ -77,7 +77,7 @@ where type IntoIter = <&'a mut L as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.collection.into_iter() + self.data.into_iter() } } @@ -92,7 +92,7 @@ impl<'a, L: OwnedLockable<'a>, I: FromIterator<L> + OwnedLockable<'a>> FromItera impl<'a, E: OwnedLockable<'a> + Extend<L>, L: OwnedLockable<'a>> Extend<L> for LockCollection<E> { fn extend<T: IntoIterator<Item = L>>(&mut self, iter: T) { - self.collection.extend(iter) + self.data.extend(iter) } } @@ -101,18 +101,35 @@ impl<'a, L: OwnedLockable<'a>> LockCollection<L> { /// /// Because the locks are owned, there's no need to do any checks for /// duplicate values. + /// + /// # Examples + /// + /// ``` + /// use happylock::{LockCollection, Mutex}; + /// + /// let lock = LockCollection::new((Mutex::new(0), Mutex::new(""))); + /// ``` #[must_use] - pub const fn new(collection: L) -> Self { - Self { collection } + pub const fn new(data: L) -> Self { + Self { data } } /// Creates a new collection of owned locks. /// /// Because the locks are owned, there's no need to do any checks for /// duplicate values. + /// + /// # Examples + /// + /// ``` + /// use happylock::{LockCollection, Mutex}; + /// + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = LockCollection::new_ref(&data); + /// ``` #[must_use] - pub const fn new_ref(collection: &L) -> LockCollection<&L> { - LockCollection { collection } + pub const fn new_ref(data: &L) -> LockCollection<&L> { + LockCollection { data } } } @@ -123,9 +140,21 @@ impl<L> LockCollection<L> { /// /// This results in undefined behavior if any locks are presented twice /// within this collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{LockCollection, Mutex}; + /// + /// let data1 = Mutex::new(0); + /// let data2 = Mutex::new(""); + /// + /// // safety: data1 and data2 refer to distinct mutexes + /// let lock = unsafe { LockCollection::new_unchecked((&data1, &data2)) }; + /// ``` #[must_use] - pub const unsafe fn new_unchecked(collection: L) -> Self { - Self { collection } + pub const unsafe fn new_unchecked(data: L) -> Self { + Self { data } } } @@ -140,37 +169,84 @@ impl<'a, L: Lockable<'a>> LockCollection<L> { /// This does a check at runtime to make sure that the collection contains /// no two copies of the same lock. This is an `O(n^2)` operation. Prefer /// [`LockCollection::new`] or [`LockCollection::new_ref`] instead. + /// + /// # Examples + /// + /// ``` + /// use happylock::{LockCollection, Mutex}; + /// + /// let data1 = Mutex::new(0); + /// let data2 = Mutex::new(""); + /// + /// // data1 and data2 refer to distinct mutexes, so this won't panic + /// let lock = LockCollection::try_new((&data1, &data2)).unwrap(); + /// ``` #[must_use] - pub fn try_new(collection: L) -> Option<Self> { - let ptrs = collection.get_ptrs(); + pub fn try_new(data: L) -> Option<Self> { + let ptrs = data.get_ptrs(); if contains_duplicates(&ptrs) { return None; } - Some(Self { collection }) + Some(Self { data }) } - /// Locks the lockable type and returns a guard that can be used to access - /// the underlying data. + /// Locks the collection + /// + /// This function returns a guard that can be used to access the underlying + /// data. When the guard is dropped, the locks in the collection are also + /// dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{LockCollection, Mutex, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = LockCollection::new((Mutex::new(0), Mutex::new(""))); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// ``` pub fn lock<'key: 'a, Key: Keyable + 'key>(&'a self, key: Key) -> LockGuard<'a, 'key, L, Key> { LockGuard { // safety: we have the thread's key - guard: unsafe { self.collection.lock() }, + guard: unsafe { self.data.lock() }, key, _phantom: PhantomData, } } - /// Attempts to lock the guard without blocking. + /// Attempts to lock the without blocking. /// /// If successful, this method returns a guard that can be used to access - /// the data. Otherwise, `None` is returned. + /// the data, and unlocks the data when it is dropped. Otherwise, `None` is + /// returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{LockCollection, Mutex, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = LockCollection::new((Mutex::new(0), Mutex::new(""))); + /// + /// match lock.try_lock(key) { + /// Some(mut guard) => { + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_lock<'key: 'a, Key: Keyable + 'key>( &'a self, key: Key, ) -> Option<LockGuard<'a, 'key, L, Key>> { // safety: we have the thread's key - unsafe { self.collection.try_lock() }.map(|guard| LockGuard { + unsafe { self.data.try_lock() }.map(|guard| LockGuard { guard, key, _phantom: PhantomData, @@ -179,6 +255,20 @@ impl<'a, L: Lockable<'a>> LockCollection<L> { /// Unlocks the underlying lockable data type, returning the key that's /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{LockCollection, Mutex, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = LockCollection::new((Mutex::new(0), Mutex::new(""))); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// let key = LockCollection::unlock(guard); + /// ``` #[allow(clippy::missing_const_for_fn)] pub fn unlock<'key: 'a, Key: Keyable + 'key>(guard: LockGuard<'a, 'key, L, Key>) -> Key { drop(guard.guard); @@ -20,7 +20,7 @@ static KEY: Lazy<ThreadLocal<AtomicLock>> = Lazy::new(ThreadLocal::new); /// The key for the current thread. /// /// Only one of these exist per thread. To get the current thread's key, call -/// [`ThreadKey::lock`]. If the `ThreadKey` is dropped, it can be reobtained. +/// [`ThreadKey::get`]. If the `ThreadKey` is dropped, it can be reobtained. pub struct ThreadKey { phantom: PhantomData<*const ()>, // implement !Send and !Sync } @@ -54,21 +54,21 @@ impl ThreadKey { /// The first time this is called, it will successfully return a /// `ThreadKey`. However, future calls to this function on the same thread /// will return [`None`], unless the key is dropped or unlocked first. + /// + /// # Examples + /// + /// ``` + /// use happylock::ThreadKey; + /// + /// let key = ThreadKey::get().unwrap(); + /// ``` #[must_use] - pub fn lock() -> Option<Self> { + pub fn get() -> Option<Self> { // safety: we just acquired the lock KEY.get_or_default().try_lock().then_some(Self { phantom: PhantomData, }) } - - /// Unlocks the `ThreadKey`. - /// - /// After this method is called, a call to [`ThreadKey::lock`] will return - /// this `ThreadKey`. - pub fn unlock(self) { - drop(self); - } } /// A dumb lock that's just a wrapper for an [`AtomicBool`]. diff --git a/src/mutex.rs b/src/mutex.rs index 0da1460..b36854b 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -33,7 +33,7 @@ pub type ParkingMutex<T> = Mutex<T, parking_lot::RawMutex>; /// [`ThreadKey`]: `crate::ThreadKey` pub struct Mutex<T: ?Sized, R> { raw: R, - value: UnsafeCell<T>, + data: UnsafeCell<T>, } /// A reference to a mutex that unlocks it when dropped diff --git a/src/mutex/guard.rs b/src/mutex/guard.rs index 5d249ee..9a309b4 100644 --- a/src/mutex/guard.rs +++ b/src/mutex/guard.rs @@ -22,7 +22,7 @@ impl<'a, T: ?Sized + 'a, R: RawMutex> Deref for MutexRef<'a, T, R> { // safety: this is the only type that can use `value`, and there's // a reference to this type, so there cannot be any mutable // references to this value. - unsafe { &*self.0.value.get() } + unsafe { &*self.0.data.get() } } } @@ -31,7 +31,7 @@ impl<'a, T: ?Sized + 'a, R: RawMutex> DerefMut for MutexRef<'a, T, R> { // safety: this is the only type that can use `value`, and we have a // mutable reference to this type, so there cannot be any other // references to this value. - unsafe { &mut *self.0.value.get() } + unsafe { &mut *self.0.data.get() } } } diff --git a/src/mutex/mutex.rs b/src/mutex/mutex.rs index 52a4848..83600b9 100644 --- a/src/mutex/mutex.rs +++ b/src/mutex/mutex.rs @@ -18,10 +18,10 @@ impl<T, R: RawMutex> Mutex<T, R> { /// let mutex = Mutex::new(0); /// ``` #[must_use] - pub const fn new(value: T) -> Self { + pub const fn new(data: T) -> Self { Self { raw: R::INIT, - value: UnsafeCell::new(value), + data: UnsafeCell::new(data), } } } @@ -79,7 +79,7 @@ impl<T, R> Mutex<T, R> { /// ``` #[must_use] pub fn into_inner(self) -> T { - self.value.into_inner() + self.data.into_inner() } } @@ -94,14 +94,14 @@ impl<T: ?Sized, R> Mutex<T, R> { /// ``` /// use happylock::{ThreadKey, Mutex}; /// - /// let key = ThreadKey::lock().unwrap(); + /// let key = ThreadKey::get().unwrap(); /// let mut mutex = Mutex::new(0); /// *mutex.get_mut() = 10; /// assert_eq!(*mutex.lock(key), 10); /// ``` #[must_use] pub fn get_mut(&mut self) -> &mut T { - self.value.get_mut() + self.data.get_mut() } } @@ -122,11 +122,11 @@ impl<T: ?Sized, R: RawMutex> Mutex<T, R> { /// let c_mutex = Arc::clone(&mutex); /// /// thread::spawn(move || { - /// let key = ThreadKey::lock().unwrap(); + /// let key = ThreadKey::get().unwrap(); /// *c_mutex.lock(key) = 10; /// }).join().expect("thread::spawn failed"); /// - /// let key = ThreadKey::lock().unwrap(); + /// let key = ThreadKey::get().unwrap(); /// assert_eq!(*mutex.lock(key), 10); /// ``` pub fn lock<'s, 'k: 's, Key: Keyable>(&'s self, key: Key) -> MutexGuard<'_, 'k, T, Key, R> { @@ -163,7 +163,7 @@ impl<T: ?Sized, R: RawMutex> Mutex<T, R> { /// let c_mutex = Arc::clone(&mutex); /// /// thread::spawn(move || { - /// let key = ThreadKey::lock().unwrap(); + /// let key = ThreadKey::get().unwrap(); /// let mut lock = c_mutex.try_lock(key); /// if let Some(mut lock) = lock { /// *lock = 10; @@ -172,7 +172,7 @@ impl<T: ?Sized, R: RawMutex> Mutex<T, R> { /// } /// }).join().expect("thread::spawn failed"); /// - /// let key = ThreadKey::lock().unwrap(); + /// let key = ThreadKey::get().unwrap(); /// assert_eq!(*mutex.lock(key), 10); /// ``` pub fn try_lock<'s, 'a: 's, 'k: 'a, Key: Keyable>( @@ -210,7 +210,7 @@ impl<T: ?Sized, R: RawMutex> Mutex<T, R> { /// ``` /// use happylock::{ThreadKey, Mutex}; /// - /// let key = ThreadKey::lock().unwrap(); + /// let key = ThreadKey::get().unwrap(); /// let mutex = Mutex::new(0); /// /// let mut guard = mutex.lock(key); diff --git a/src/rwlock.rs b/src/rwlock.rs index 06862cd..fb40a71 100644 --- a/src/rwlock.rs +++ b/src/rwlock.rs @@ -19,25 +19,80 @@ pub type SpinRwLock<T> = RwLock<T, spin::RwLock<()>>; #[cfg(feature = "parking_lot")] pub type ParkingRwLock<T> = RwLock<T, parking_lot::RawRwLock>; +/// A reader-writer lock +/// +/// This type of lock allows a number of readers or at most one writer at any +/// point in time. The write portion of thislock typically allows modification +/// of the underlying data (exclusive access) and the read portion of this lock +/// typically allows for read-only access (shared access). +/// +/// In comparison, a [`Mutex`] does not distinguish between readers or writers +/// that acquire the lock, therefore blocking any threads waiting for the lock +/// to become available. An `RwLock` will allow any number of readers to +/// acquire the lock as long as a writer is not holding the lock. +/// +/// The type parameter T represents the data that this lock protects. It is +/// required that T satisfies [`Send`] to be shared across threads and [`Sync`] +/// to allow concurrent access through readers. The RAII guard returned from +/// the locking methods implement [`Deref`] (and [`DerefMut`] for the `write` +/// methods) to allow access to the content of the lock. +/// +/// Locking the mutex on a thread that already locked it is impossible, due to +/// the requirement of the [`ThreadKey`]. Therefore, this will never deadlock. +/// +/// +/// [`ThreadKey`]: `crate::ThreadKey` +/// [`Mutex`]: `crate::mutex::Mutex` +/// [`Deref`]: `std::ops::Deref` +/// [`DerefMut`]: `std::ops::DerefMut` pub struct RwLock<T: ?Sized, R> { raw: R, - value: UnsafeCell<T>, + data: UnsafeCell<T>, } +/// Grants read access to an [`RwLock`] +/// +/// This structure is designed to be used in a [`LockCollection`] to indicate +/// that only read access is needed to the data. +/// +/// [`LockCollection`]: `crate::LockCollection` pub struct ReadLock<'a, T: ?Sized, R>(&'a RwLock<T, R>); +/// Grants write access to an [`RwLock`] +/// +/// This structure is designed to be used in a [`LockCollection`] to indicate +/// that write access is needed to the data. +/// +/// [`LockCollection`]: `crate::LockCollection` pub struct WriteLock<'a, T: ?Sized, R>(&'a RwLock<T, R>); +/// RAII structure that unlocks the shared read access to a [`RwLock`] pub struct RwLockReadRef<'a, T: ?Sized, R: RawRwLock>(&'a RwLock<T, R>); +/// RAII structure that unlocks the exclusive write access to a [`RwLock`] pub struct RwLockWriteRef<'a, T: ?Sized, R: RawRwLock>(&'a RwLock<T, R>); +/// RAII structure used to release the shared read access of a lock when +/// dropped. +/// +/// This structure is created by the [`read`] and [`try_read`] methods on +/// [`RwLock`]. +/// +/// [`read`]: `RwLock::read` +/// [`try_read`]: `RwLock::try_read` pub struct RwLockReadGuard<'a, 'key, T: ?Sized, Key: Keyable + 'key, R: RawRwLock> { rwlock: RwLockReadRef<'a, T, R>, thread_key: Key, _phantom: PhantomData<&'key ()>, } +/// RAII structure used to release the exclusive write access of a lock when +/// dropped. +/// +/// This structure is created by the [`write`] and [`try_write`] methods on +/// [`RwLock`] +/// +/// [`try_write`]: `RwLock::try_write` pub struct RwLockWriteGuard<'a, 'key, T: ?Sized, Key: Keyable + 'key, R: RawRwLock> { rwlock: RwLockWriteRef<'a, T, R>, thread_key: Key, diff --git a/src/rwlock/read_guard.rs b/src/rwlock/read_guard.rs index e967420..d8db9b9 100644 --- a/src/rwlock/read_guard.rs +++ b/src/rwlock/read_guard.rs @@ -14,7 +14,7 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> Deref for RwLockReadRef<'a, T, R> { // safety: this is the only type that can use `value`, and there's // a reference to this type, so there cannot be any mutable // references to this value. - unsafe { &*self.0.value.get() } + unsafe { &*self.0.data.get() } } } diff --git a/src/rwlock/read_lock.rs b/src/rwlock/read_lock.rs index dbab8de..176fc01 100644 --- a/src/rwlock/read_lock.rs +++ b/src/rwlock/read_lock.rs @@ -6,9 +6,25 @@ use crate::key::Keyable; use super::{ReadLock, RwLock, RwLockReadGuard, RwLockReadRef}; -impl<'a, T: ?Sized, R> Debug for ReadLock<'a, T, R> { +impl<'a, T: ?Sized + Debug, R: RawRwLock> Debug for ReadLock<'a, T, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("ReadLock<{}>", std::any::type_name::<T>())) + // safety: this is just a try lock, and the value is dropped + // immediately after, so there's no risk of blocking ourselves + // or any other threads + if let Some(value) = unsafe { self.try_lock_no_key() } { + f.debug_struct("ReadLock").field("data", &&*value).finish() + } else { + struct LockedPlaceholder; + impl Debug for LockedPlaceholder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("<locked>") + } + } + + f.debug_struct("ReadLock") + .field("data", &LockedPlaceholder) + .finish() + } } } @@ -25,6 +41,16 @@ impl<'a, T: ?Sized, R> AsRef<RwLock<T, R>> for ReadLock<'a, T, R> { } impl<'a, T: ?Sized, R> ReadLock<'a, T, R> { + /// Creates a new `ReadLock` which accesses the given [`RwLock`] + /// + /// # Examples + /// + /// ``` + /// use happylock::{rwlock::ReadLock, RwLock}; + /// + /// let lock = RwLock::new(5); + /// let read_lock = ReadLock::new(&lock); + /// ``` #[must_use] pub const fn new(rwlock: &'a RwLock<T, R>) -> Self { Self(rwlock) @@ -32,6 +58,8 @@ impl<'a, T: ?Sized, R> ReadLock<'a, T, R> { } impl<'a, T: ?Sized, R: RawRwLock> ReadLock<'a, T, R> { + /// Locks the underlying [`RwLock`] with shared read access, blocking the + /// current thread until it can be acquired. pub fn lock<'s, 'key: 's, Key: Keyable + 'key>( &'s self, key: Key, @@ -39,10 +67,14 @@ impl<'a, T: ?Sized, R: RawRwLock> ReadLock<'a, T, R> { self.0.read(key) } + /// Creates a shared lock without a key. Locking this without exclusive + /// access to the key is undefined behavior. pub(crate) unsafe fn lock_no_key(&self) -> RwLockReadRef<'_, T, R> { self.0.read_no_key() } + /// Attempts to acquire the underlying [`RwLock`] with shared read access + /// without blocking. pub fn try_lock<'s, 'key: 's, Key: Keyable + 'key>( &'s self, key: Key, @@ -50,10 +82,14 @@ impl<'a, T: ?Sized, R: RawRwLock> ReadLock<'a, T, R> { self.0.try_read(key) } + /// Attempts to create an exclusive lock without a key. Locking this + /// without exclusive access to the key is undefined behavior. pub(crate) unsafe fn try_lock_no_key(&self) -> Option<RwLockReadRef<'_, T, R>> { self.0.try_read_no_key() } + /// Immediately drops the guard, and consequentlyreleases the shared lock + /// on the underlying [`RwLock`]. pub fn unlock<'key, Key: Keyable + 'key>(guard: RwLockReadGuard<'_, 'key, T, Key, R>) -> Key { RwLock::unlock_read(guard) } diff --git a/src/rwlock/rwlock.rs b/src/rwlock/rwlock.rs index c1d1792..556d6bf 100644 --- a/src/rwlock/rwlock.rs +++ b/src/rwlock/rwlock.rs @@ -8,10 +8,19 @@ use crate::key::Keyable; use super::{RwLock, RwLockReadGuard, RwLockReadRef, RwLockWriteGuard, RwLockWriteRef}; impl<T, R: RawRwLock> RwLock<T, R> { + /// Creates a new instance of an `RwLock<T>` which is unlocked. + /// + /// # Examples + /// + /// ``` + /// use happylock::RwLock; + /// + /// let lock = RwLock::new(5); + /// ``` #[must_use] - pub const fn new(value: T) -> Self { + pub const fn new(data: T) -> Self { Self { - value: UnsafeCell::new(value), + data: UnsafeCell::new(data), raw: R::INIT, } } @@ -59,17 +68,51 @@ impl<T: ?Sized, R> AsMut<T> for RwLock<T, R> { impl<T, R> RwLock<T, R> { pub fn into_inner(self) -> T { - self.value.into_inner() + self.data.into_inner() } } impl<T: ?Sized, R> RwLock<T, R> { pub fn get_mut(&mut self) -> &mut T { - self.value.get_mut() + self.data.get_mut() } } impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { + /// Locks this `RwLock` with shared read access, blocking the current + /// thread until it can be acquired. + /// + /// The calling thread will be blocked until there are no more writers + /// which hold the lock. There may be other readers currently inside the + /// lock when this method returns. + /// + /// Returns an RAII guard which will release this thread's shared access + /// once it is dropped. + /// + /// Because this method takes a [`ThreadKey`], it's not possible for this + /// method to cause a deadlock. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use std::thread; + /// use happylock::{RwLock, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = Arc::new(RwLock::new(1)); + /// let c_lock = Arc::clone(&lock); + /// + /// let n = lock.read(key); + /// assert_eq!(*n, 1); + /// + /// thread::spawn(move || { + /// let key = ThreadKey::get().unwrap(); + /// let r = c_lock.read(key); + /// }).join().unwrap(); + /// ``` + /// + /// [`ThreadKey`]: `crate::ThreadKey` pub fn read<'s, 'key: 's, Key: Keyable>( &'s self, key: Key, @@ -82,6 +125,8 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { } } + /// Creates a shared lock without a key. Locking this without exclusive + /// access to the key is undefined behavior. pub(crate) unsafe fn read_no_key(&self) -> RwLockReadRef<'_, T, R> { self.raw.lock_shared(); @@ -89,6 +134,26 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { RwLockReadRef(self) } + /// Attempts to acquire this `RwLock` with shared read access without + /// blocking. + /// + /// If the access could not be granted at this time, then `None` is + /// returned. Otherwise, an RAII guard is returned which will release the + /// shared access when it is dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = RwLock::new(1); + /// + /// match lock.try_read(key) { + /// Some(n) => assert_eq!(*n, 1), + /// None => unreachable!(), + /// }; + /// ``` pub fn try_read<'s, 'key: 's, Key: Keyable>( &'s self, key: Key, @@ -103,6 +168,8 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { } } + /// Attempts to create a shared lock without a key. Locking this without + /// exclusive access to the key is undefined behavior. pub(crate) unsafe fn try_read_no_key(&self) -> Option<RwLockReadRef<'_, T, R>> { if self.raw.try_lock_shared() { // safety: the lock is locked first @@ -112,6 +179,31 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { } } + /// Locks this `RwLock` with exclusive write access, blocking the current + /// until it can be acquired. + /// + /// This function will not return while other writers or readers currently + /// have access to the lock. + /// + /// Returns an RAII guard which will drop the write access of this `RwLock` + /// when dropped. + /// + /// Because this method takes a [`ThreadKey`], it's not possible for this + /// method to cause a deadlock. + /// + /// # Examples + /// + /// ``` + /// use happylock::{ThreadKey, RwLock}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = RwLock::new(1); + /// + /// let mut n = lock.write(key); + /// *n += 2; + /// ``` + /// + /// [`ThreadKey`]: `crate::ThreadKey` pub fn write<'s, 'key: 's, Key: Keyable>( &'s self, key: Key, @@ -124,6 +216,8 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { } } + /// Creates an exclusive lock without a key. Locking this without exclusive + /// access to the key is undefined behavior. pub(crate) unsafe fn write_no_key(&self) -> RwLockWriteRef<'_, T, R> { self.raw.lock_exclusive(); @@ -131,6 +225,27 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { RwLockWriteRef(self) } + /// Attempts to lock this `RwLock` with exclusive write access. + /// + /// This function does not block. If the lock could not be acquired at this + /// time, then `None` is returned. Otherwise an RAII guard is returned + /// which will release the lock when it is dropped. + /// + /// This function does not provide any guarantees with respect to the + /// ordering of whether contentious readers or writers will acquire the + /// lock first. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = RwLock::new(1); + /// + /// let n = lock.read(key); + /// assert_eq!(*n, 1); + /// ``` pub fn try_write<'s, 'key: 's, Key: Keyable>( &'s self, key: Key, @@ -145,6 +260,8 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { } } + /// Attempts to create an exclusive lock without a key. Locking this + /// without exclusive access to the key is undefined behavior. pub(crate) unsafe fn try_write_no_key(&self) -> Option<RwLockWriteRef<'_, T, R>> { if self.raw.try_lock_exclusive() { // safety: the lock is locked first @@ -154,14 +271,36 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { } } + /// Unlocks shared access on the `RwLock`. This is undefined behavior is + /// the data is still accessible. pub(super) unsafe fn force_unlock_read(&self) { self.raw.unlock_shared(); } + /// Unlocks exclusive access on the `RwLock`. This is undefined behavior is + /// the data is still accessible. pub(super) unsafe fn force_unlock_write(&self) { self.raw.unlock_exclusive(); } + /// Immediately drops the guard, and consequently releases the shared lock. + /// + /// This function is equivalent to calling [`drop`] on the guard, except + /// that it returns the key that was used to create it. Alternately, the + /// guard will be automatically dropped when it goes out of scope. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = RwLock::new(0); + /// + /// let mut guard = lock.read(key); + /// assert_eq!(*guard, 0); + /// let key = RwLock::unlock_read(guard); + /// ``` pub fn unlock_read<'key, Key: Keyable + 'key>( guard: RwLockReadGuard<'_, 'key, T, Key, R>, ) -> Key { @@ -171,6 +310,25 @@ impl<T: ?Sized, R: RawRwLock> RwLock<T, R> { guard.thread_key } + /// Immediately drops the guard, and consequently releases the exclusive + /// lock. + /// + /// This function is equivalent to calling [`drop`] on the guard, except + /// that it returns the key that was used to create it. Alternately, the + /// guard will be automatically dropped when it goes out of scope. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let lock = RwLock::new(0); + /// + /// let mut guard = lock.write(key); + /// *guard += 20; + /// let key = RwLock::unlock_write(guard); + /// ``` pub fn unlock_write<'key, Key: Keyable + 'key>( guard: RwLockWriteGuard<'_, 'key, T, Key, R>, ) -> Key { diff --git a/src/rwlock/write_guard.rs b/src/rwlock/write_guard.rs index 8f5feb4..dd168bf 100644 --- a/src/rwlock/write_guard.rs +++ b/src/rwlock/write_guard.rs @@ -14,7 +14,7 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> Deref for RwLockWriteRef<'a, T, R> { // safety: this is the only type that can use `value`, and there's // a reference to this type, so there cannot be any mutable // references to this value. - unsafe { &*self.0.value.get() } + unsafe { &*self.0.data.get() } } } @@ -23,7 +23,7 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> DerefMut for RwLockWriteRef<'a, T, R> { // safety: this is the only type that can use `value`, and we have a // mutable reference to this type, so there cannot be any other // references to this value. - unsafe { &mut *self.0.value.get() } + unsafe { &mut *self.0.data.get() } } } diff --git a/src/rwlock/write_lock.rs b/src/rwlock/write_lock.rs index dd204f5..0275a70 100644 --- a/src/rwlock/write_lock.rs +++ b/src/rwlock/write_lock.rs @@ -25,6 +25,16 @@ impl<'a, T: ?Sized, R> AsRef<RwLock<T, R>> for WriteLock<'a, T, R> { } impl<'a, T: ?Sized, R> WriteLock<'a, T, R> { + /// Creates a new `WriteLock` which accesses the given [`RwLock`] + /// + /// # Examples + /// + /// ``` + /// use happylock::{rwlock::WriteLock, RwLock}; + /// + /// let lock = RwLock::new(5); + /// let write_lock = WriteLock::new(&lock); + /// ``` #[must_use] pub const fn new(rwlock: &'a RwLock<T, R>) -> Self { Self(rwlock) @@ -32,6 +42,8 @@ impl<'a, T: ?Sized, R> WriteLock<'a, T, R> { } impl<'a, T: ?Sized, R: RawRwLock> WriteLock<'a, T, R> { + /// Locks the underlying [`RwLock`] with exclusive write access, blocking + /// the current until it can be acquired. pub fn lock<'s, 'key: 's, Key: Keyable + 'key>( &'s self, key: Key, @@ -39,10 +51,13 @@ impl<'a, T: ?Sized, R: RawRwLock> WriteLock<'a, T, R> { self.0.write(key) } + /// Creates an exclusive lock without a key. Locking this without exclusive + /// access to the key is undefined behavior. pub(crate) unsafe fn lock_no_key(&self) -> RwLockWriteRef<'_, T, R> { self.0.write_no_key() } + /// Attempts to lock the underlying [`RwLock`] with exclusive write access. pub fn try_lock<'s, 'key: 's, Key: Keyable + 'key>( &'s self, key: Key, @@ -50,10 +65,14 @@ impl<'a, T: ?Sized, R: RawRwLock> WriteLock<'a, T, R> { self.0.try_write(key) } + /// Attempts to create an exclusive lock without a key. Locking this + /// without exclusive access to the key is undefined behavior. pub(crate) unsafe fn try_lock_no_key(&self) -> Option<RwLockWriteRef<'_, T, R>> { self.0.try_write_no_key() } + /// Immediately drops the guard, and consequently releases the exclusive + /// lock. pub fn unlock<'key, Key: Keyable + 'key>(guard: RwLockWriteGuard<'_, 'key, T, Key, R>) -> Key { RwLock::unlock_write(guard) } |
