1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::declare_interior_mutable_const)]
#![allow(clippy::semicolon_if_nothing_returned)]
#![allow(clippy::module_inception)]
//! As it turns out, the Rust borrow checker is powerful enough that, if the
//! standard library supported it, we could've made deadlocks undefined
//! behavior. This library currently serves as a proof of concept for how that
//! would work.
//!
//! # Theory
//!
//! There are four conditions necessary for a deadlock to occur. In order to
//! prevent deadlocks, we need to prevent one of the following:
//!
//! 1. mutual exclusion
//! 2. non-preemptive allocation
//! 3. circular wait
//! 4. **partial allocation**
//!
//! This library seeks to solve **partial allocation** by requiring total
//! allocation. All of the resources a thread needs must be allocated at the
//! same time. In order to request new resources, the old resources must be
//! dropped first. Requesting multiple resources at once is atomic. You either
//! get all of the requested resources or none at all.
//!
//! # Performance
//!
//! **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.
//!
//! **Avoid using distinct lock orders for [`LockCollection`].** The problem is
//! that this library must iterate through the list of locks, and not complete
//! until every single one of them is unlocked. This also means that attempting
//! to lock multiple mutexes gives you a lower chance of ever running. Only one
//! needs to be locked for the operation to need a reset. This problem can be
//! prevented by not doing that in your code. Resources should be obtained in
//! the same order on every thread.
//!
//! # Examples
//!
//! Simple example:
//! ```
//! use std::thread;
//! use happylock::{Mutex, ThreadKey};
//!
//! const N: usize = 10;
//!
//! static DATA: Mutex<i32> = Mutex::new(0);
//!
//! for _ in 0..N {
//! thread::spawn(move || {
//! // each thread gets one thread key
//! let key = ThreadKey::get().unwrap();
//!
//! // unlocking a mutex requires a ThreadKey
//! let mut data = DATA.lock(key);
//! *data += 1;
//!
//! // the key is unlocked at the end of the scope
//! });
//! }
//!
//! let key = ThreadKey::get().unwrap();
//! let data = DATA.lock(key);
//! println!("{}", *data);
//! ```
//!
//! To lock multiple mutexes at a time, create a [`LockCollection`]:
//!
//! ```
//! use std::thread;
//! use happylock::{LockCollection, Mutex, ThreadKey};
//!
//! const N: usize = 10;
//!
//! static DATA_1: Mutex<i32> = Mutex::new(0);
//! static DATA_2: Mutex<String> = Mutex::new(String::new());
//!
//! for _ in 0..N {
//! thread::spawn(move || {
//! let key = ThreadKey::get().unwrap();
//!
//! // happylock ensures at runtime there are no duplicate locks
//! let collection = LockCollection::try_new((&DATA_1, &DATA_2)).unwrap();
//! let mut guard = collection.lock(key);
//!
//! *guard.1 = (100 - *guard.0).to_string();
//! *guard.0 += 1;
//! });
//! }
//!
//! let key = ThreadKey::get().unwrap();
//! let data = LockCollection::try_new((&DATA_1, &DATA_2)).unwrap();
//! let data = data.lock(key);
//! println!("{}", *data.0);
//! println!("{}", *data.1);
//! ```
mod collection;
mod key;
mod lockable;
pub mod mutex;
pub mod rwlock;
pub use collection::LockCollection;
pub use key::{Keyable, ThreadKey};
pub use lockable::{Lockable, OwnedLockable};
#[cfg(feature = "spin")]
pub use mutex::SpinLock;
/// A mutual exclusion primitive useful for protecting shared data, which cannot deadlock.
///
/// By default, this uses `parking_lot` as a backend.
#[cfg(feature = "parking_lot")]
pub type Mutex<T> = mutex::Mutex<T, parking_lot::RawMutex>;
/// A reader-writer lock
///
/// By default, this uses `parking_lot` as a backend.
#[cfg(feature = "parking_lot")]
pub type RwLock<T> = rwlock::RwLock<T, parking_lot::RawRwLock>;