Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mutex.rs to mc-sgx-sync crate #40

Merged
merged 3 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
343 changes: 343 additions & 0 deletions sync/src/mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
// 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
nick-mobilecoin marked this conversation as resolved.
Show resolved Hide resolved
/// 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);
/// ```
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.
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.
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");
nick-mobilecoin marked this conversation as resolved.
Show resolved Hide resolved
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> {
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
2 changes: 1 addition & 1 deletion sync/src/sys.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;
6 changes: 6 additions & 0 deletions sync/src/sys/locks.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