mc_sgx_sync/rwlock.rs
1// Copyright (c) The Rust Foundation
2// Copyright (c) 2023 The MobileCoin Foundation
3
4//! rwlock.rs implementation more or less copied from
5//! [rust source](https://github.com/rust-lang/rust.git) at
6//! [606c3907](https://github.com/rust-lang/rust/commit/606c3907251397a42e23d3e60de31be9d32525d5)
7//!
8//! Differences:
9//! - The imports were changed to work with the `mc-sgx` crates.
10//! - The stable attributes have been removed
11//! - The unstable attributes have been removed
12//! - Removed examples that were not possible in an SGX enclave have been omitted
13//! - Ran `cargo fmt`
14//! - Removed unnecessary unsafe blocks
15
16use crate::sys::locks as sys;
17use crate::{poison, LockResult, TryLockError, TryLockResult};
18use core::cell::UnsafeCell;
19use core::fmt;
20use core::ops::{Deref, DerefMut};
21use core::ptr::NonNull;
22
23/// A reader-writer lock
24///
25/// This type of lock allows a number of readers or at most one writer at any
26/// point in time. The write portion of this lock typically allows modification
27/// of the underlying data (exclusive access) and the read portion of this lock
28/// typically allows for read-only access (shared access).
29///
30/// In comparison, a [`Mutex`] does not distinguish between readers or writers
31/// that acquire the lock, therefore blocking any threads waiting for the lock to
32/// become available. An `RwLock` will allow any number of readers to acquire the
33/// lock as long as a writer is not holding the lock.
34///
35/// The priority policy of the lock is dependent on the underlying operating
36/// system's implementation, and this type does not guarantee that any
37/// particular policy will be used. In particular, a writer which is waiting to
38/// acquire the lock in `write` might or might not block concurrent calls to
39/// `read`, e.g.:
40///
41/// <details><summary>Potential deadlock example</summary>
42///
43/// ```text
44/// // Thread 1 | // Thread 2
45/// let _rg = lock.read(); |
46/// | // will block
47/// | let _wg = lock.write();
48/// // may deadlock |
49/// let _rg = lock.read(); |
50/// ```
51/// </details>
52///
53/// The type parameter `T` represents the data that this lock protects. It is
54/// required that `T` satisfies [`Send`] to be shared across threads and
55/// [`Sync`] to allow concurrent access through readers. The RAII guards
56/// returned from the locking methods implement [`Deref`] (and [`DerefMut`]
57/// for the `write` methods) to allow access to the content of the lock.
58///
59/// # Poisoning
60///
61/// An `RwLock`, like [`Mutex`], will become poisoned on a panic. Note, however,
62/// that an `RwLock` may only be poisoned if a panic occurs while it is locked
63/// exclusively (write mode). If a panic occurs in any reader, then the lock
64/// will not be poisoned.
65///
66/// # Examples
67///
68/// ```
69/// use mc_sgx_sync::RwLock;
70///
71/// let lock = RwLock::new(5);
72///
73/// // many reader locks can be held at once
74/// {
75/// let r1 = lock.read().unwrap();
76/// let r2 = lock.read().unwrap();
77/// assert_eq!(*r1, 5);
78/// assert_eq!(*r2, 5);
79/// } // read locks are dropped at this point
80///
81/// // only one write lock may be held, however
82/// {
83/// let mut w = lock.write().unwrap();
84/// *w += 1;
85/// assert_eq!(*w, 6);
86/// } // write lock is dropped here
87/// ```
88///
89/// [`Mutex`]: super::Mutex
90pub struct RwLock<T: ?Sized> {
91 inner: sys::RwLock,
92 poison: poison::Flag,
93 data: UnsafeCell<T>,
94}
95
96unsafe impl<T: ?Sized + Send> Send for RwLock<T> {}
97unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}
98
99/// RAII structure used to release the shared read access of a lock when
100/// dropped.
101///
102/// This structure is created by the [`read`] and [`try_read`] methods on
103/// [`RwLock`].
104///
105/// [`read`]: RwLock::read
106/// [`try_read`]: RwLock::try_read
107#[must_use = "if unused the RwLock will immediately unlock"]
108#[clippy::has_significant_drop]
109pub struct RwLockReadGuard<'a, T: ?Sized + 'a> {
110 // NB: we use a pointer instead of `&'a T` to avoid `noalias` violations, because a
111 // `Ref` argument doesn't hold immutability for its whole scope, only until it drops.
112 // `NonNull` is also covariant over `T`, just like we would have with `&T`. `NonNull`
113 // is preferable over `const* T` to allow for niche optimization.
114 data: NonNull<T>,
115 inner_lock: &'a sys::RwLock,
116}
117
118impl<T: ?Sized> !Send for RwLockReadGuard<'_, T> {}
119unsafe impl<T: ?Sized + Sync> Sync for RwLockReadGuard<'_, T> {}
120
121/// RAII structure used to release the exclusive write access of a lock when
122/// dropped.
123///
124/// This structure is created by the [`write`] and [`try_write`] methods
125/// on [`RwLock`].
126///
127/// [`write`]: RwLock::write
128/// [`try_write`]: RwLock::try_write
129#[must_use = "if unused the RwLock will immediately unlock"]
130#[clippy::has_significant_drop]
131pub struct RwLockWriteGuard<'a, T: ?Sized + 'a> {
132 lock: &'a RwLock<T>,
133 poison: poison::Guard,
134}
135
136impl<T: ?Sized> !Send for RwLockWriteGuard<'_, T> {}
137unsafe impl<T: ?Sized + Sync> Sync for RwLockWriteGuard<'_, T> {}
138
139impl<T> RwLock<T> {
140 /// Creates a new instance of an `RwLock<T>` which is unlocked.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// use mc_sgx_sync::RwLock;
146 ///
147 /// let lock = RwLock::new(5);
148 /// ```
149 pub const fn new(t: T) -> RwLock<T> {
150 RwLock {
151 inner: sys::RwLock::new(),
152 poison: poison::Flag::new(),
153 data: UnsafeCell::new(t),
154 }
155 }
156}
157
158impl<T: ?Sized> RwLock<T> {
159 /// Locks this `RwLock` with shared read access, blocking the current thread
160 /// until it can be acquired.
161 ///
162 /// The calling thread will be blocked until there are no more writers which
163 /// hold the lock. There may be other readers currently inside the lock when
164 /// this method returns. This method does not provide any guarantees with
165 /// respect to the ordering of whether contentious readers or writers will
166 /// acquire the lock first.
167 ///
168 /// Returns an RAII guard which will release this thread's shared access
169 /// once it is dropped.
170 ///
171 /// # Errors
172 ///
173 /// This function will return an error if the `RwLock` is poisoned. An
174 /// `RwLock` is poisoned whenever a writer panics while holding an exclusive
175 /// lock. The failure will occur immediately after the lock has been
176 /// acquired.
177 ///
178 /// # Panics
179 ///
180 /// This function might panic when called if the lock is already held by the current thread.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use mc_sgx_sync::RwLock;
186 ///
187 /// let lock = RwLock::new(1);
188 ///
189 /// let n = lock.read().unwrap();
190 /// assert_eq!(*n, 1);
191 /// ```
192 pub fn read(&self) -> LockResult<RwLockReadGuard<'_, T>> {
193 unsafe {
194 self.inner.read();
195 RwLockReadGuard::new(self)
196 }
197 }
198
199 /// Attempts to acquire this `RwLock` with shared read access.
200 ///
201 /// If the access could not be granted at this time, then `Err` is returned.
202 /// Otherwise, an RAII guard is returned which will release the shared access
203 /// when it is dropped.
204 ///
205 /// This function does not block.
206 ///
207 /// This function does not provide any guarantees with respect to the ordering
208 /// of whether contentious readers or writers will acquire the lock first.
209 ///
210 /// # Errors
211 ///
212 /// This function will return the [`Poisoned`] error if the `RwLock` is
213 /// poisoned. An `RwLock` is poisoned whenever a writer panics while holding
214 /// an exclusive lock. `Poisoned` will only be returned if the lock would
215 /// have otherwise been acquired.
216 ///
217 /// This function will return the [`WouldBlock`] error if the `RwLock` could
218 /// not be acquired because it was already locked exclusively.
219 ///
220 /// [`Poisoned`]: TryLockError::Poisoned
221 /// [`WouldBlock`]: TryLockError::WouldBlock
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use mc_sgx_sync::RwLock;
227 ///
228 /// let lock = RwLock::new(1);
229 ///
230 /// match lock.try_read() {
231 /// Ok(n) => assert_eq!(*n, 1),
232 /// Err(_) => unreachable!(),
233 /// };
234 /// ```
235 pub fn try_read(&self) -> TryLockResult<RwLockReadGuard<'_, T>> {
236 unsafe {
237 if self.inner.try_read() {
238 Ok(RwLockReadGuard::new(self)?)
239 } else {
240 Err(TryLockError::WouldBlock)
241 }
242 }
243 }
244
245 /// Locks this `RwLock` with exclusive write access, blocking the current
246 /// thread until it can be acquired.
247 ///
248 /// This function will not return while other writers or other readers
249 /// currently have access to the lock.
250 ///
251 /// Returns an RAII guard which will drop the write access of this `RwLock`
252 /// when dropped.
253 ///
254 /// # Errors
255 ///
256 /// This function will return an error if the `RwLock` is poisoned. An
257 /// `RwLock` is poisoned whenever a writer panics while holding an exclusive
258 /// lock. An error will be returned when the lock is acquired.
259 ///
260 /// # Panics
261 ///
262 /// This function might panic when called if the lock is already held by the current thread.
263 ///
264 /// # Examples
265 ///
266 /// ```
267 /// use mc_sgx_sync::RwLock;
268 ///
269 /// let lock = RwLock::new(1);
270 ///
271 /// let mut n = lock.write().unwrap();
272 /// *n = 2;
273 ///
274 /// assert!(lock.try_read().is_err());
275 /// ```
276 pub fn write(&self) -> LockResult<RwLockWriteGuard<'_, T>> {
277 unsafe {
278 self.inner.write();
279 RwLockWriteGuard::new(self)
280 }
281 }
282
283 /// Attempts to lock this `RwLock` with exclusive write access.
284 ///
285 /// If the lock could not be acquired at this time, then `Err` is returned.
286 /// Otherwise, an RAII guard is returned which will release the lock when
287 /// it is dropped.
288 ///
289 /// This function does not block.
290 ///
291 /// This function does not provide any guarantees with respect to the ordering
292 /// of whether contentious readers or writers will acquire the lock first.
293 ///
294 /// # Errors
295 ///
296 /// This function will return the [`Poisoned`] error if the `RwLock` is
297 /// poisoned. An `RwLock` is poisoned whenever a writer panics while holding
298 /// an exclusive lock. `Poisoned` will only be returned if the lock would
299 /// have otherwise been acquired.
300 ///
301 /// This function will return the [`WouldBlock`] error if the `RwLock` could
302 /// not be acquired because it was already locked exclusively.
303 ///
304 /// [`Poisoned`]: TryLockError::Poisoned
305 /// [`WouldBlock`]: TryLockError::WouldBlock
306 ///
307 ///
308 /// # Examples
309 ///
310 /// ```
311 /// use mc_sgx_sync::RwLock;
312 ///
313 /// let lock = RwLock::new(1);
314 ///
315 /// let n = lock.read().unwrap();
316 /// assert_eq!(*n, 1);
317 ///
318 /// assert!(lock.try_write().is_err());
319 /// ```
320 pub fn try_write(&self) -> TryLockResult<RwLockWriteGuard<'_, T>> {
321 unsafe {
322 if self.inner.try_write() {
323 Ok(RwLockWriteGuard::new(self)?)
324 } else {
325 Err(TryLockError::WouldBlock)
326 }
327 }
328 }
329
330 /// Determines whether the lock is poisoned.
331 ///
332 /// If another thread is active, the lock can still become poisoned at any
333 /// time. You should not trust a `false` value for program correctness
334 /// without additional synchronization.
335 pub fn is_poisoned(&self) -> bool {
336 self.poison.get()
337 }
338
339 /// Clear the poisoned state from a lock
340 ///
341 /// If the lock is poisoned, it will remain poisoned until this function is called. This allows
342 /// recovering from a poisoned state and marking that it has recovered. For example, if the
343 /// value is overwritten by a known-good value, then the mutex can be marked as un-poisoned. Or
344 /// possibly, the value could be inspected to determine if it is in a consistent state, and if
345 /// so the poison is removed.
346 pub fn clear_poison(&self) {
347 self.poison.clear();
348 }
349
350 /// Consumes this `RwLock`, returning the underlying data.
351 ///
352 /// # Errors
353 ///
354 /// This function will return an error if the `RwLock` is poisoned. An
355 /// `RwLock` is poisoned whenever a writer panics while holding an exclusive
356 /// lock. An error will only be returned if the lock would have otherwise
357 /// been acquired.
358 ///
359 /// # Examples
360 ///
361 /// ```
362 /// use mc_sgx_sync::RwLock;
363 ///
364 /// let lock = RwLock::new(String::new());
365 /// {
366 /// let mut s = lock.write().unwrap();
367 /// *s = "modified".to_owned();
368 /// }
369 /// assert_eq!(lock.into_inner().unwrap(), "modified");
370 /// ```
371 pub fn into_inner(self) -> LockResult<T>
372 where
373 T: Sized,
374 {
375 let data = self.data.into_inner();
376 poison::map_result(self.poison.borrow(), |()| data)
377 }
378
379 /// Returns a mutable reference to the underlying data.
380 ///
381 /// Since this call borrows the `RwLock` mutably, no actual locking needs to
382 /// take place -- the mutable borrow statically guarantees no locks exist.
383 ///
384 /// # Errors
385 ///
386 /// This function will return an error if the `RwLock` is poisoned. An
387 /// `RwLock` is poisoned whenever a writer panics while holding an exclusive
388 /// lock. An error will only be returned if the lock would have otherwise
389 /// been acquired.
390 ///
391 /// # Examples
392 ///
393 /// ```
394 /// use mc_sgx_sync::RwLock;
395 ///
396 /// let mut lock = RwLock::new(0);
397 /// *lock.get_mut().unwrap() = 10;
398 /// assert_eq!(*lock.read().unwrap(), 10);
399 /// ```
400 pub fn get_mut(&mut self) -> LockResult<&mut T> {
401 let data = self.data.get_mut();
402 poison::map_result(self.poison.borrow(), |()| data)
403 }
404}
405
406impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLock<T> {
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 let mut d = f.debug_struct("RwLock");
409 match self.try_read() {
410 Ok(guard) => {
411 d.field("data", &&*guard);
412 }
413 Err(TryLockError::Poisoned(err)) => {
414 d.field("data", &&**err.get_ref());
415 }
416 Err(TryLockError::WouldBlock) => {
417 struct LockedPlaceholder;
418 impl fmt::Debug for LockedPlaceholder {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 f.write_str("<locked>")
421 }
422 }
423 d.field("data", &LockedPlaceholder);
424 }
425 }
426 d.field("poisoned", &self.poison.get());
427 d.finish_non_exhaustive()
428 }
429}
430
431impl<T: Default> Default for RwLock<T> {
432 /// Creates a new `RwLock<T>`, with the `Default` value for T.
433 fn default() -> RwLock<T> {
434 RwLock::new(Default::default())
435 }
436}
437
438impl<T> From<T> for RwLock<T> {
439 /// Creates a new instance of an `RwLock<T>` which is unlocked.
440 /// This is equivalent to [`RwLock::new`].
441 fn from(t: T) -> Self {
442 RwLock::new(t)
443 }
444}
445
446impl<'rwlock, T: ?Sized> RwLockReadGuard<'rwlock, T> {
447 /// Create a new instance of `RwLockReadGuard<T>` from a `RwLock<T>`.
448 // SAFETY: if and only if `lock.inner.read()` (or `lock.inner.try_read()`) has been
449 // successfully called from the same thread before instantiating this object.
450 unsafe fn new(lock: &'rwlock RwLock<T>) -> LockResult<RwLockReadGuard<'rwlock, T>> {
451 poison::map_result(lock.poison.borrow(), |()| RwLockReadGuard {
452 data: NonNull::new_unchecked(lock.data.get()),
453 inner_lock: &lock.inner,
454 })
455 }
456}
457
458impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
459 /// Create a new instance of `RwLockWriteGuard<T>` from a `RwLock<T>`.
460 // SAFETY: if and only if `lock.inner.write()` (or `lock.inner.try_write()`) has been
461 // successfully called from the same thread before instantiating this object.
462 unsafe fn new(lock: &'rwlock RwLock<T>) -> LockResult<RwLockWriteGuard<'rwlock, T>> {
463 poison::map_result(lock.poison.guard(), |guard| RwLockWriteGuard {
464 lock,
465 poison: guard,
466 })
467 }
468}
469
470impl<T: fmt::Debug> fmt::Debug for RwLockReadGuard<'_, T> {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 (**self).fmt(f)
473 }
474}
475
476impl<T: ?Sized + fmt::Display> fmt::Display for RwLockReadGuard<'_, T> {
477 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478 (**self).fmt(f)
479 }
480}
481
482impl<T: fmt::Debug> fmt::Debug for RwLockWriteGuard<'_, T> {
483 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484 (**self).fmt(f)
485 }
486}
487
488impl<T: ?Sized + fmt::Display> fmt::Display for RwLockWriteGuard<'_, T> {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 (**self).fmt(f)
491 }
492}
493
494impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
495 type Target = T;
496
497 fn deref(&self) -> &T {
498 // SAFETY: the conditions of `RwLockGuard::new` were satisfied when created.
499 unsafe { self.data.as_ref() }
500 }
501}
502
503impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {
504 type Target = T;
505
506 fn deref(&self) -> &T {
507 // SAFETY: the conditions of `RwLockWriteGuard::new` were satisfied when created.
508 unsafe { &*self.lock.data.get() }
509 }
510}
511
512impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
513 fn deref_mut(&mut self) -> &mut T {
514 // SAFETY: the conditions of `RwLockWriteGuard::new` were satisfied when created.
515 unsafe { &mut *self.lock.data.get() }
516 }
517}
518
519impl<T: ?Sized> Drop for RwLockReadGuard<'_, T> {
520 fn drop(&mut self) {
521 self.inner_lock.read_unlock();
522 }
523}
524
525impl<T: ?Sized> Drop for RwLockWriteGuard<'_, T> {
526 fn drop(&mut self) {
527 self.lock.poison.done(&self.poison);
528 self.lock.inner.write_unlock();
529 }
530}