Skip to content

Commit

Permalink
Add mutex.rs to mc-sgx-sync crate
Browse files Browse the repository at this point in the history
Add `mutex.rs` implementation which more or less copies from
[rust source](https://github.com/rust-lang/rust.git) at
[606c3907](rust-lang/rust@606c390)
  • Loading branch information
nick-mobilecoin committed Jan 17, 2023
1 parent 1d47a22 commit 538e33b
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 7 deletions.
7 changes: 5 additions & 2 deletions sync/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
347 changes: 347 additions & 0 deletions sync/src/mutex.rs
Original file line number Diff line number Diff line change
@@ -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<T: ?Sized> {
inner: sys::Mutex,
poison: poison::Flag,
data: UnsafeCell<T>,
}

// these are the only places where `T: Send` matters; all other
// functionality works fine on a single thread.
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}

/// 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<T>,
poison: poison::Guard,
}

impl<T: ?Sized> !Send for MutexGuard<'_, T> {}
unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}

impl<T> Mutex<T> {
/// 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<T> {
Mutex {
inner: sys::Mutex::new(),
poison: poison::Flag::new(),
data: UnsafeCell::new(t),
}
}
}

impl<T: ?Sized> Mutex<T> {
/// 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<MutexGuard<'_, T>> {
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<MutexGuard<'_, T>> {
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<T>
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<T> From<T> for Mutex<T> {
/// 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<T: ?Sized + Default> Default for Mutex<T> {
/// Creates a `Mutex<T>`, with the `Default` value for T.
fn default() -> Mutex<T> {
Mutex::new(Default::default())
}
}

impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
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("<locked>")
}
}
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<T>) -> LockResult<MutexGuard<'mutex, T>> {
poison::map_result(lock.poison.guard(), |guard| MutexGuard {
lock,
poison: guard,
})
}
}

impl<T: ?Sized> Deref for MutexGuard<'_, T> {
type Target = T;

fn deref(&self) -> &T {
unsafe { &*self.lock.data.get() }
}
}

impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.lock.data.get() }
}
}

impl<T: ?Sized> Drop for MutexGuard<'_, T> {
#[inline]
fn drop(&mut self) {
self.lock.poison.done(&self.poison);
self.lock.inner.unlock();
}
}

impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}

impl<T: ?Sized + fmt::Display> 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
}
2 changes: 0 additions & 2 deletions sync/src/poison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
6 changes: 6 additions & 0 deletions sync/src/sys/locks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2023 The MobileCoin Foundation

//! Platform specific concurrency locking primitives.
mod mutex;
pub(crate) use mutex::Mutex;
2 changes: 0 additions & 2 deletions sync/src/sys/mutex.rs → sync/src/sys/locks/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
//! Per the docs and discussions Rust mutexes are not re-entrant,
//! <https://github.com/rust-lang/rust/issues/32260>.
#![allow(dead_code)]

use mc_sgx_tstdc::Mutex as SgxMutex;

/// The mutex backend to use with the common Rust std lib Mutex interface
Expand Down
2 changes: 1 addition & 1 deletion sync/src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit 538e33b

Please sign in to comment.