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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! 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 just 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 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
//! the requested resources or none at all.
//!
//! As an optimization, this library also often prevents **circular wait**.
//! Many collections sort the locks in order of their memory address. As long
//! as the locks are always acquired in that order, then time doesn't need to
//! be wasted on releasing locks after a failure and re-acquiring them later.
//!
//! # 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);
//! ```
//!
//! In many cases, the [`LockCollection::new`] or [`LockCollection::new_ref`]
//! method can be used, improving performance.
//!
//! ```rust
//! use std::thread;
//! use happylock::{LockCollection, Mutex, ThreadKey};
//!
//! const N: usize = 32;
//!
//! static DATA: [Mutex<i32>; 2] = [Mutex::new(0), Mutex::new(1)];
//!
//! for _ in 0..N {
//! thread::spawn(move || {
//! let key = ThreadKey::get().unwrap();
//!
//! // a reference to a type that implements `OwnedLockable` will never
//! // contain duplicates, so no duplicate checking is needed.
//! let collection = LockCollection::new_ref(&DATA);
//! let mut guard = collection.lock(key);
//!
//! let x = *guard[1];
//! *guard[1] += *guard[0];
//! *guard[0] = x;
//! });
//! }
//!
//! let key = ThreadKey::get().unwrap();
//! let data = LockCollection::new_ref(&DATA);
//! let data = data.lock(key);
//! println!("{}", data[0]);
//! 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::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.
//!
//! **Consider [`OwnedLockCollection`].** This will almost always be the
//! fastest lock collection. It doesn't expose the underlying collection
//! immutably, which means that it will always be locked in the same order, and
//! doesn't need any sorting.
//!
//! **Avoid [`LockCollection::try_new`].** This constructor will check to make
//! sure that the collection contains no duplicate locks. In most cases, this
//! is O(nlogn), where n is the number of locks in the collections but in the
//! case of [`RetryingLockCollection`], it's close to O(n).
//! [`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.
//!
//! **Know how to use [`RetryingLockCollection`].** This collection doesn't do
//! any sorting, but uses a wasteful lock algorithm. It can't rely on the order
//! of the locks to be the same across threads, so if it finds a lock that it
//! can't acquire without blocking, it'll first release all of the locks it
//! already acquired to avoid blocking other threads. This is wasteful because
//! this algorithm may end up re-acquiring the same lock multiple times. To
//! avoid this, ensure that (1) the first lock in the collection is always the
//! first lock in any collection it appears in, and (2) the other locks in the
//! collection are always preceded by that first lock. This will prevent any
//! wasted time from re-acquiring locks. If you're unsure, [`LockCollection`]
//! is a sensible default.
//!
//! [`OwnedLockable`]: `lockable::OwnedLockable`
//! [`OwnedLockCollection`]: `collection::OwnedLockCollection`
//! [`RetryingLockCollection`]: `collection::RetryingLockCollection`
pub use ;
pub use SpinLock;
// Personally, I think re-exports look ugly in the rust documentation, so I
// went with type aliases instead.
/// A collection of locks that can be acquired simultaneously.
///
/// This re-exports [`BoxedLockCollection`] as a sensible default.
///
/// [`BoxedLockCollection`]: collection::BoxedLockCollection
pub type LockCollection<L> = BoxedLockCollection;
/// A re-export for [`poisonable::Poisonable`]
pub type Poisonable<L> = Poisonable;
/// A mutual exclusion primitive useful for protecting shared data, which cannot deadlock.
///
/// By default, this uses `parking_lot` as a backend.
pub type Mutex<T> = Mutex;
/// A reader-writer lock
///
/// By default, this uses `parking_lot` as a backend.
pub type RwLock<T> = RwLock;