diff options
| -rw-r--r-- | src/lib.rs | 15 | ||||
| -rw-r--r-- | src/lock.rs | 7 | ||||
| -rw-r--r-- | src/mutex.rs | 238 |
3 files changed, 245 insertions, 15 deletions
@@ -1,5 +1,6 @@ #![warn(clippy::pedantic)] #![warn(clippy::nursery)] +#![allow(clippy::module_name_repetitions)] use std::any::type_name; use std::fmt::{self, Debug}; @@ -9,9 +10,12 @@ use once_cell::sync::Lazy; use thread_local::ThreadLocal; mod lock; +pub mod mutex; use lock::{Key, Lock}; +pub use mutex::Mutex; + static KEY: Lazy<ThreadLocal<Lock>> = Lazy::new(ThreadLocal::new); /// The key for the current thread. @@ -50,15 +54,4 @@ impl ThreadKey { pub fn unlock(key: Self) { drop(key); } - - /// Unlocks the `ThreadKey` without consuming it. - /// - /// # Safety - /// - /// This should only be called if the `ThreadKey` to the lock has been - /// "lost". That means the program no longer has a reference to the key, - /// but it has not been dropped. - pub unsafe fn force_unlock() { - KEY.get_or_default().force_unlock(); - } } diff --git a/src/lock.rs b/src/lock.rs index df83323..c69ce21 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -50,14 +50,13 @@ impl Lock { (!self.is_locked.fetch_or(true, Ordering::Acquire)).then_some(Key::new(self)) } - /// Unlock the lock, without a key. + /// Forcibly unlocks the `Lock`. /// /// # Safety /// /// This should only be called if the key to the lock has been "lost". That - /// means the program no longer has a reference to the key, but it has not - /// been dropped. - pub unsafe fn force_unlock(&self) { + /// means the program no longer has a reference to the key. + unsafe fn force_unlock(&self) { self.is_locked.store(false, Ordering::Release); } diff --git a/src/mutex.rs b/src/mutex.rs new file mode 100644 index 0000000..a152eda --- /dev/null +++ b/src/mutex.rs @@ -0,0 +1,238 @@ +use std::cell::UnsafeCell; +use std::ops::{Deref, DerefMut}; + +use crate::ThreadKey; + +/// Implements a raw C-like mutex. +/// +/// # Safety +/// +/// It cannot be possible to lock the mutex when it is already locked. +pub unsafe trait RawMutex { + /// The initial value for an unlocked mutex + const INIT: Self; + + /// Lock the mutex, blocking until the lock is acquired + fn lock(&self); + + /// Attempt to lock the mutex without blocking. + /// + /// Returns `true` if successful, `false` otherwise. + fn try_lock(&self) -> bool; + + /// Checks whether the mutex is currently locked or not + fn is_locked(&self) -> bool; + + /// Unlock the mutex. + /// + /// # Safety + /// + /// The lock must be acquired in the current context. + unsafe fn unlock(&self); +} + +/// A mutual exclusion primitive useful for protecting shared data, which +/// cannot deadlock. +/// +/// This mutex will block threads waiting for the lock to become available. +/// Each mutex has a type parameter which represents the data that it is +/// protecting. The data can only be accessed through the [`MutexGuard`]s +/// returned from [`lock`] and [`try_lock`], which guarantees that the data is +/// only ever accessed when the mutex is locked. +/// +/// [`lock`]: `Mutex::lock` +/// [`try_lock`]: `Mutex::try_lock` +pub struct Mutex<R, T: ?Sized> { + raw: R, + value: UnsafeCell<T>, +} + +/// A reference to a mutex that unlocks it when dropped +struct MutexRef<'a, R: RawMutex, T: ?Sized + 'a>(&'a Mutex<R, T>); + +impl<'a, R: RawMutex, T: ?Sized + 'a> Drop for MutexRef<'a, R, T> { + fn drop(&mut self) { + // safety: this guard is being destroyed, so the data cannot be + // accessed without locking again + unsafe { self.0.force_unlock() } + } +} + +/// An RAII implementation of a “scoped lock” of a mutex. When this structure +/// is dropped (falls out of scope), the lock will be unlocked. +/// +/// This is created by calling the [`lock`] and [`try_lock`] methods on [`Mutex`] +/// +/// [`lock`]: `Mutex::lock` +/// [`try_lock`]: `Mutex::try_lock` +pub struct MutexGuard<'a, R: RawMutex, T: ?Sized + 'a> { + mutex: MutexRef<'a, R, T>, + thread_key: ThreadKey, +} + +impl<'a, R: RawMutex, T: ?Sized + 'a> Deref for MutexGuard<'a, R, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // 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.mutex().value.get() } + } +} + +impl<'a, R: RawMutex, T: ?Sized + 'a> DerefMut for MutexGuard<'a, R, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // 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.mutex().value.get() } + } +} + +impl<'a, R: RawMutex, T: ?Sized + 'a> MutexGuard<'a, R, T> { + /// Create a guard to the given mutex + const unsafe fn new(mutex: &'a Mutex<R, T>, thread_key: ThreadKey) -> Self { + Self { + mutex: MutexRef(mutex), + thread_key, + } + } + + /// Get a reference to the mutex this is guarding + const fn mutex(&self) -> &'a Mutex<R, T> { + self.mutex.0 + } +} + +impl<R: RawMutex, T> Mutex<R, T> { + /// Create a new unlocked `Mutex`. + /// + /// # Examples + /// + /// ``` + /// use happylock::Mutex; + /// + /// let mutex = Mutex::new(0); + /// ``` + pub const fn new(value: T) -> Self { + Self { + raw: R::INIT, + value: UnsafeCell::new(value), + } + } +} + +impl<R: RawMutex, T: ?Sized> Mutex<R, T> { + /// Block the thread until this mutex can be locked, and lock it. + /// + /// Upon returning, the thread is the only thread with a lock on the + /// `Mutex`. A [`MutexGuard`] is returned to allow a scoped unlock of this + /// `Mutex`. When the guard is dropped, this `Mutex` will unlock. + /// + /// Locking the mutex on a thread that already locked it is impossible, due + /// to the requirement of the [`ThreadKey`]. Therefore, this will never + /// deadlock. When the [`MutexGuard`] is dropped, the [`ThreadKey`] can be + /// reobtained by calling [`ThreadKey::lock`]. You can also get it + /// by calling [`Mutex::unlock`]. + /// + /// # Examples + /// + /// ``` + /// use std::{thread, sync::Arc}; + /// use happylock::{Mutex, ThreadKey}; + /// + /// let mutex = Arc::new(Mutex::new(0)); + /// let c_mutex = Arc::clone(&mutex); + /// + /// thread::spawn(move || { + /// let key = ThreadKey::lock().unwrap(); + /// *c_mutex.lock(key) = 10; + /// }).join().expect("thread::spawn failed"); + /// + /// let key = ThreadKey::lock().unwrap(); + /// assert_eq!(*mutex.lock(key), 10); + /// ``` + pub fn lock(&self, key: ThreadKey) -> MutexGuard<'_, R, T> { + self.raw.lock(); + + // safety: we just locked the mutex + unsafe { MutexGuard::new(self, key) } + } + + /// Attempts to lock the `Mutex` without blocking. + /// + /// # Errors + /// + /// Returns [`Err`] if the `Mutex` cannot be locked without blocking. + /// + /// # Examples + /// + /// ``` + /// use std::{thread, sync::Arc}; + /// use happylock::{Mutex, ThreadKey}; + /// + /// let mutex = Arc::new(Mutex::new(0)); + /// let c_mutex = Arc::clone(mutex); + /// + /// thread::spawn(move || { + /// let key = ThradKey::lock().unwrap(); + /// let mut lock = c_mutex.try_lock(); + /// if let Ok(lock) = lock { + /// **mutex = 10; + /// } else { + /// println!("try_lock failed"); + /// } + /// }).join().expect("thread::spawn failed"); + /// + /// let key = ThreadKey::lock().unwrap(); + /// assert_eq!(*mutex.lock(key), 10); + /// ``` + pub fn try_lock(&self, key: ThreadKey) -> Result<MutexGuard<'_, R, T>, ThreadKey> { + if self.raw.try_lock() { + // safety: we just locked the mutex + Ok(unsafe { MutexGuard::new(self, key) }) + } else { + Err(key) + } + } + + /// Forcibly unlocks the `Lock`. + /// + /// # Safety + /// + /// This should only be called if there are no references to any + /// [`MutexGuard`]s for this mutex in the program. + unsafe fn force_unlock(&self) { + self.raw.unlock(); + } + + /// Consumes the [`MutexGuard`], and consequently unlocks its `Mutex`. + /// + /// This returns the [`ThreadKey`] that was used to lock the `Mutex`, which + /// means that [`ThreadKey::lock`] does not need to be called, and will in + /// fact return [`None`] if the [`ThreadKey`] returned by this function is + /// not dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{ThreadKey, Mutex}; + /// + /// let key = ThreadKey::lock().unwrap(); + /// let mutex = Mutex::new(0); + /// + /// let guard = mutex.lock(key); + /// *guard += 20; + /// + /// let key = Mutex::unlock(guard); + /// ``` + #[allow(clippy::missing_const_for_fn)] + #[must_use] + pub fn unlock(guard: MutexGuard<'_, R, T>) -> ThreadKey { + guard.thread_key + } +} + +unsafe impl<R: Send, T: ?Sized + Send> Send for Mutex<R, T> {} +unsafe impl<R: RawMutex + Sync, T: ?Sized + Send> Sync for Mutex<R, T> {} |
