summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs15
-rw-r--r--src/lock.rs7
-rw-r--r--src/mutex.rs238
3 files changed, 245 insertions, 15 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 0f0e330..615086d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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> {}