summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2024-03-11 16:33:26 -0400
committerMica White <botahamec@outlook.com>2024-03-11 16:33:26 -0400
commit462fc2d9aab8f0cba680caec344e4c388e9901b1 (patch)
tree6b401c5ed4920c2ec8093d5c49976fe0b72573c2
parent5eaa4fe1d3bfcda696122ba3d6b4914dba19ef96 (diff)
Documentation
-rw-r--r--README.md12
-rw-r--r--examples/basic.rs4
-rw-r--r--examples/dining_philosophers.rs2
-rw-r--r--examples/double_mutex.rs4
-rw-r--r--examples/list.rs4
-rw-r--r--src/collection.rs4
-rw-r--r--src/collection/collection.rs132
-rw-r--r--src/key.rs20
-rw-r--r--src/mutex.rs2
-rw-r--r--src/mutex/guard.rs4
-rw-r--r--src/mutex/mutex.rs20
-rw-r--r--src/rwlock.rs57
-rw-r--r--src/rwlock/read_guard.rs2
-rw-r--r--src/rwlock/read_lock.rs40
-rw-r--r--src/rwlock/rwlock.rs166
-rw-r--r--src/rwlock/write_guard.rs4
-rw-r--r--src/rwlock/write_lock.rs19
17 files changed, 428 insertions, 68 deletions
diff --git a/README.md b/README.md
index 54e0be1..787bea6 100644
--- a/README.md
+++ b/README.md
@@ -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);
diff --git a/src/key.rs b/src/key.rs
index 887ac29..1cfa209 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -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)
}