From 7aebc104bdd2597fddafcc4c713806581711a0f0 Mon Sep 17 00:00:00 2001 From: Nick Santana Date: Thu, 12 Jan 2023 08:14:02 -0800 Subject: [PATCH 1/3] Add `mutex.rs` to `mc-sgx-sync` crate Add `mutex.rs` implementation which more or less copies from [rust source](https://github.com/rust-lang/rust.git) at [606c3907](https://github.com/rust-lang/rust/commit/606c3907251397a42e23d3e60de31be9d32525d5) --- sync/src/lib.rs | 7 +- sync/src/mutex.rs | 347 ++++++++++++++++++++++++++++++ sync/src/poison.rs | 2 - sync/src/sys.rs | 2 +- sync/src/sys/locks/mod.rs | 6 + sync/src/sys/{ => locks}/mutex.rs | 2 - 6 files changed, 359 insertions(+), 7 deletions(-) create mode 100644 sync/src/mutex.rs create mode 100644 sync/src/sys/locks/mod.rs rename sync/src/sys/{ => locks}/mutex.rs (98%) diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 083bf2f..e89865b 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -3,7 +3,10 @@ #![doc = include_str!("../README.md")] #![deny(missing_docs, missing_debug_implementations)] #![no_std] -#![feature(error_in_core)] +#![feature(error_in_core, must_not_suspend, negative_impls)] -pub mod poison; +mod mutex; +mod poison; +pub use mutex::{Mutex, MutexGuard}; +pub use poison::{LockResult, PoisonError, TryLockError, TryLockResult}; mod sys; diff --git a/sync/src/mutex.rs b/sync/src/mutex.rs new file mode 100644 index 0000000..9cd1b52 --- /dev/null +++ b/sync/src/mutex.rs @@ -0,0 +1,347 @@ +// Copyright (c) The Rust Foundation +// Copyright (c) 2023 The MobileCoin Foundation + +//! mutex.rs implementation more or less copied from +//! [rust source](https://github.com/rust-lang/rust.git) at +//! [606c3907](https://github.com/rust-lang/rust/commit/606c3907251397a42e23d3e60de31be9d32525d5) +//! +//! Differences: +//! - The imports were changed to work with the `mc-sgx` crates. +//! - The stable attributes have been removed +//! - The unstable attributes have been removed +//! - Items that are crate only were converted from `pub` to `pub(crate)` +//! - Removed examples that were not possible in an SGX enclave have been omitted +//! - Ran `cargo fmt` +//! - Removed unnecessary unsafe blocks + +#![allow(dead_code)] + +use crate::sys::locks as sys; +use crate::{poison, LockResult, TryLockError, TryLockResult}; +use core::cell::UnsafeCell; +use core::fmt; +use core::ops::{Deref, DerefMut}; + +/// A mutual exclusion primitive useful for protecting shared data +/// +/// This mutex will block threads waiting for the lock to become available. The +/// mutex can be created via a [`new`] constructor. Each mutex has a type parameter +/// which represents the data that it is protecting. The data can only be accessed +/// through the RAII guards returned from [`lock`] and [`try_lock`], which +/// guarantees that the data is only ever accessed when the mutex is locked. +/// +/// # Poisoning +/// +/// The mutexes in this module implement a strategy called "poisoning" where a +/// mutex is considered poisoned whenever a thread panics while holding the +/// mutex. Once a mutex is poisoned, all other threads are unable to access the +/// data by default as it is likely tainted (some invariant is not being +/// upheld). +/// +/// For a mutex, this means that the [`lock`] and [`try_lock`] methods return a +/// [`Result`] which indicates whether a mutex has been poisoned or not. Most +/// usage of a mutex will simply [`unwrap()`] these results, propagating panics +/// among threads to ensure that a possibly invalid invariant is not witnessed. +/// +/// A poisoned mutex, however, does not prevent all access to the underlying +/// data. The [`PoisonError`] type has an [`into_inner`] method which will return +/// the guard that would have otherwise been returned on a successful lock. This +/// allows access to the data, despite the lock being poisoned. +/// +/// [`new`]: Self::new +/// [`lock`]: Self::lock +/// [`try_lock`]: Self::try_lock +/// [`unwrap()`]: Result::unwrap +/// [`PoisonError`]: super::PoisonError +/// [`into_inner`]: super::PoisonError::into_inner +pub struct Mutex { + inner: sys::Mutex, + poison: poison::Flag, + data: UnsafeCell, +} + +// these are the only places where `T: Send` matters; all other +// functionality works fine on a single thread. +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +/// An RAII implementation of a "scoped lock" of a mutex. When this structure is +/// dropped (falls out of scope), the lock will be unlocked. +/// +/// The data protected by the mutex can be accessed through this guard via its +/// [`Deref`] and [`DerefMut`] implementations. +/// +/// This structure is created by the [`lock`] and [`try_lock`] methods on +/// [`Mutex`]. +/// +/// [`lock`]: Mutex::lock +/// [`try_lock`]: Mutex::try_lock +#[must_use = "if unused the Mutex will immediately unlock"] +#[must_not_suspend = "holding a MutexGuard across suspend \ + points can cause deadlocks, delays, \ + and cause Futures to not implement `Send`"] +#[clippy::has_significant_drop] +pub struct MutexGuard<'a, T: ?Sized + 'a> { + lock: &'a Mutex, + poison: poison::Guard, +} + +impl !Send for MutexGuard<'_, T> {} +unsafe impl Sync for MutexGuard<'_, T> {} + +impl Mutex { + /// Creates a new mutex in an unlocked state ready for use. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Mutex; + /// + /// let mutex = Mutex::new(0); + /// ``` + #[inline] + pub const fn new(t: T) -> Mutex { + Mutex { + inner: sys::Mutex::new(), + poison: poison::Flag::new(), + data: UnsafeCell::new(t), + } + } +} + +impl Mutex { + /// Acquires a mutex, blocking the current thread until it is able to do so. + /// + /// This function will block the local thread until it is available to acquire + /// the mutex. Upon returning, the thread is the only thread with the lock + /// held. An RAII guard is returned to allow scoped unlock of the lock. When + /// the guard goes out of scope, the mutex will be unlocked. + /// + /// The exact behavior on locking a mutex in the thread which already holds + /// the lock is left unspecified. However, this function will not return on + /// the second call (it might panic or deadlock, for example). + /// + /// # Errors + /// + /// If another user of this mutex panicked while holding the mutex, then + /// this call will return an error once the mutex is acquired. + /// + /// # Panics + /// + /// This function might panic when called if the lock is already held by + /// the current thread. + pub fn lock(&self) -> LockResult> { + unsafe { + self.inner.lock(); + MutexGuard::new(self) + } + } + + /// Attempts to acquire this lock. + /// + /// If the lock could not be acquired at this time, then [`Err`] is returned. + /// Otherwise, an RAII guard is returned. The lock will be unlocked when the + /// guard is dropped. + /// + /// This function does not block. + /// + /// # Errors + /// + /// If another user of this mutex panicked while holding the mutex, then + /// this call will return the [`Poisoned`] error if the mutex would + /// otherwise be acquired. + /// + /// If the mutex could not be acquired because it is already locked, then + /// this call will return the [`WouldBlock`] error. + /// + /// [`Poisoned`]: TryLockError::Poisoned + /// [`WouldBlock`]: TryLockError::WouldBlock + pub fn try_lock(&self) -> TryLockResult> { + unsafe { + if self.inner.try_lock() { + Ok(MutexGuard::new(self)?) + } else { + Err(TryLockError::WouldBlock) + } + } + } + + /// Immediately drops the guard, and consequently unlocks the mutex. + /// + /// This function is equivalent to calling [`drop`] on the guard but is more self-documenting. + /// Alternately, the guard will be automatically dropped when it goes out of scope. + /// + /// ``` + /// #![feature(mutex_unlock)] + /// + /// use std::sync::Mutex; + /// let mutex = Mutex::new(0); + /// + /// let mut guard = mutex.lock().unwrap(); + /// *guard += 20; + /// Mutex::unlock(guard); + /// ``` + pub fn unlock(guard: MutexGuard<'_, T>) { + drop(guard); + } + + /// Determines whether the mutex is poisoned. + /// + /// If another thread is active, the mutex can still become poisoned at any + /// time. You should not trust a `false` value for program correctness + /// without additional synchronization. + #[inline] + pub fn is_poisoned(&self) -> bool { + self.poison.get() + } + + /// Clear the poisoned state from a mutex + /// + /// If the mutex is poisoned, it will remain poisoned until this function is called. This + /// allows recovering from a poisoned state and marking that it has recovered. For example, if + /// the value is overwritten by a known-good value, then the mutex can be marked as + /// un-poisoned. Or possibly, the value could be inspected to determine if it is in a + /// consistent state, and if so the poison is removed. + #[inline] + pub fn clear_poison(&self) { + self.poison.clear(); + } + + /// Consumes this mutex, returning the underlying data. + /// + /// # Errors + /// + /// If another user of this mutex panicked while holding the mutex, then + /// this call will return an error instead. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Mutex; + /// + /// let mutex = Mutex::new(0); + /// assert_eq!(mutex.into_inner().unwrap(), 0); + /// ``` + pub fn into_inner(self) -> LockResult + where + T: Sized, + { + let data = self.data.into_inner(); + poison::map_result(self.poison.borrow(), |()| data) + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the `Mutex` mutably, no actual locking needs to + /// take place -- the mutable borrow statically guarantees no locks exist. + /// + /// # Errors + /// + /// If another user of this mutex panicked while holding the mutex, then + /// this call will return an error instead. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Mutex; + /// + /// let mut mutex = Mutex::new(0); + /// *mutex.get_mut().unwrap() = 10; + /// assert_eq!(*mutex.lock().unwrap(), 10); + /// ``` + pub fn get_mut(&mut self) -> LockResult<&mut T> { + let data = self.data.get_mut(); + poison::map_result(self.poison.borrow(), |()| data) + } +} + +impl From for Mutex { + /// Creates a new mutex in an unlocked state ready for use. + /// This is equivalent to [`Mutex::new`]. + fn from(t: T) -> Self { + Mutex::new(t) + } +} + +impl Default for Mutex { + /// Creates a `Mutex`, with the `Default` value for T. + fn default() -> Mutex { + Mutex::new(Default::default()) + } +} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("Mutex"); + match self.try_lock() { + Ok(guard) => { + d.field("data", &&*guard); + } + Err(TryLockError::Poisoned(err)) => { + d.field("data", &&**err.get_ref()); + } + Err(TryLockError::WouldBlock) => { + struct LockedPlaceholder; + impl fmt::Debug for LockedPlaceholder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("") + } + } + d.field("data", &LockedPlaceholder); + } + } + d.field("poisoned", &self.poison.get()); + d.finish_non_exhaustive() + } +} + +impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> { + unsafe fn new(lock: &'mutex Mutex) -> LockResult> { + poison::map_result(lock.poison.guard(), |guard| MutexGuard { + lock, + poison: guard, + }) + } +} + +impl Deref for MutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.lock.data.get() } + } +} + +impl DerefMut for MutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.lock.data.get() } + } +} + +impl Drop for MutexGuard<'_, T> { + #[inline] + fn drop(&mut self) { + self.lock.poison.done(&self.poison); + self.lock.inner.unlock(); + } +} + +impl fmt::Debug for MutexGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for MutexGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +pub(crate) fn guard_lock<'a, T: ?Sized>(guard: &MutexGuard<'a, T>) -> &'a sys::Mutex { + &guard.lock.inner +} + +pub(crate) fn guard_poison<'a, T: ?Sized>(guard: &MutexGuard<'a, T>) -> &'a poison::Flag { + &guard.lock.poison +} diff --git a/sync/src/poison.rs b/sync/src/poison.rs index d5837da..2f827ee 100644 --- a/sync/src/poison.rs +++ b/sync/src/poison.rs @@ -12,8 +12,6 @@ //! - Examples that were not possible in an SGX enclave have been omitted //! - Ran `cargo fmt` -#![allow(dead_code)] - use core::error::Error; use core::fmt; use core::sync::atomic::{AtomicBool, Ordering}; diff --git a/sync/src/sys.rs b/sync/src/sys.rs index 586decc..52634da 100644 --- a/sync/src/sys.rs +++ b/sync/src/sys.rs @@ -9,4 +9,4 @@ //! Having this allows us to re-use the higher level code modules from //! [`::std`](https://github.com/rust-lang/rust/tree/master/library/std/src) -mod mutex; +pub(crate) mod locks; diff --git a/sync/src/sys/locks/mod.rs b/sync/src/sys/locks/mod.rs new file mode 100644 index 0000000..39dc689 --- /dev/null +++ b/sync/src/sys/locks/mod.rs @@ -0,0 +1,6 @@ +// Copyright (c) 2023 The MobileCoin Foundation + +//! Platform specific concurrency locking primitives. + +mod mutex; +pub(crate) use mutex::Mutex; diff --git a/sync/src/sys/mutex.rs b/sync/src/sys/locks/mutex.rs similarity index 98% rename from sync/src/sys/mutex.rs rename to sync/src/sys/locks/mutex.rs index 069e3de..cf35547 100644 --- a/sync/src/sys/mutex.rs +++ b/sync/src/sys/locks/mutex.rs @@ -5,8 +5,6 @@ //! Per the docs and discussions Rust mutexes are not re-entrant, //! . -#![allow(dead_code)] - use mc_sgx_tstdc::Mutex as SgxMutex; /// The mutex backend to use with the common Rust std lib Mutex interface From fc92d676c509973e243bdf56715ebe7318fb595e Mon Sep 17 00:00:00 2001 From: Nick Santana Date: Tue, 24 Jan 2023 07:58:24 -0800 Subject: [PATCH 2/3] Remove inline usage Also fix some typos --- sync/src/mutex.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sync/src/mutex.rs b/sync/src/mutex.rs index 9cd1b52..48c20b4 100644 --- a/sync/src/mutex.rs +++ b/sync/src/mutex.rs @@ -60,7 +60,7 @@ pub struct Mutex { data: UnsafeCell, } -// these are the only places where `T: Send` matters; all other +// These are the only places where `T: Send` matters; all other // functionality works fine on a single thread. unsafe impl Send for Mutex {} unsafe impl Sync for Mutex {} @@ -99,7 +99,6 @@ impl Mutex { /// /// let mutex = Mutex::new(0); /// ``` - #[inline] pub const fn new(t: T) -> Mutex { Mutex { inner: sys::Mutex::new(), @@ -190,7 +189,6 @@ impl Mutex { /// If another thread is active, the mutex can still become poisoned at any /// time. You should not trust a `false` value for program correctness /// without additional synchronization. - #[inline] pub fn is_poisoned(&self) -> bool { self.poison.get() } @@ -199,10 +197,9 @@ impl Mutex { /// /// If the mutex is poisoned, it will remain poisoned until this function is called. This /// allows recovering from a poisoned state and marking that it has recovered. For example, if - /// the value is overwritten by a known-good value, then the mutex can be marked as + /// the value is overwritten by a known good value, then the mutex can be marked as /// un-poisoned. Or possibly, the value could be inspected to determine if it is in a /// consistent state, and if so the poison is removed. - #[inline] pub fn clear_poison(&self) { self.poison.clear(); } @@ -319,7 +316,6 @@ impl DerefMut for MutexGuard<'_, T> { } impl Drop for MutexGuard<'_, T> { - #[inline] fn drop(&mut self) { self.lock.poison.done(&self.poison); self.lock.inner.unlock(); From dc33a43d53bf82c15ee723bf935963bdc8362353 Mon Sep 17 00:00:00 2001 From: Nick Santana Date: Thu, 26 Jan 2023 07:31:48 -0800 Subject: [PATCH 3/3] Move `sync/src/sys/locks/mod.rs` to `sync/src/sys/locks.rs` Update the root module for `sync/src/sys/locks` to match newer rust file naming convention, https://doc.rust-lang.org/reference/items/modules.html#module-source-filenames --- sync/src/sys/{locks/mod.rs => locks.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sync/src/sys/{locks/mod.rs => locks.rs} (100%) diff --git a/sync/src/sys/locks/mod.rs b/sync/src/sys/locks.rs similarity index 100% rename from sync/src/sys/locks/mod.rs rename to sync/src/sys/locks.rs