Skip to content

Commit

Permalink
Add Condvar to mc-sgx-tstdc (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-mobilecoin authored Jan 25, 2023
2 parents 957abb0 + a9d62a9 commit 655298e
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 0 deletions.
133 changes: 133 additions & 0 deletions tstdc/src/condvar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) 2023 The MobileCoin Foundation

//! Condition variable functionality for use inside of an SGX enclave
use crate::Mutex;
use core::cell::UnsafeCell;
use mc_sgx_tstdc_sys::{
sgx_thread_cond_broadcast, sgx_thread_cond_destroy, sgx_thread_cond_signal,
sgx_thread_cond_wait,
};
use mc_sgx_tstdc_sys_types::{sgx_thread_cond_t, SGX_THREAD_COND_INITIALIZER};

/// Errors when interacting with [`Condvar`]s
#[derive(Copy, Clone, Debug, displaydoc::Display, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum Error {
/// Invalid operation on the condition variable
Invalid,
/// The paired mutex is locked by another thread
MutexLock,
/// Ran out of memory
NoMemory,
}

type Result<T> = core::result::Result<T, Error>;

/// Rust wrapper for an SGX SDK condition variable used inside of an enclave.
///
/// A condition variable indicates an event and has no value. More precisely,
/// one cannot store a value into nor retrieve a value from a condition
/// variable. If a thread must wait for an event to occur, that thread waits on
/// the corresponding condition variable. If another thread causes an event to
/// occur, that thread signals the corresponding condition variable.
///
/// [`Condvar`]s are paired with a [`Mutex`]. The [`Mutex`] is locked prior to
/// waiting on the [`Condvar`]. Internally the [`Mutex`] will be freed while the
/// thread waits on the [`Condvar`]. When the thread returns from
/// [`Condvar::wait()`] the [`Mutex`] will be locked by the thread.
// SAFETY: The `sgx_thread_cond_*` C functions utilize a spinlock to prevent
// concurrent access to the underlying `sgx_thread_cond_t`
#[derive(Debug, Default)]
pub struct Condvar(UnsafeCell<sgx_thread_cond_t>);

unsafe impl Send for Condvar {}
unsafe impl Sync for Condvar {}

impl Condvar {
/// Create a new condition variable
pub const fn new() -> Self {
Self(UnsafeCell::new(SGX_THREAD_COND_INITIALIZER))
}

/// Blocks the current thread until this condition variable receives a
/// notification.
///
/// # Arguments:
/// * `mutex` - The [`Mutex`] used to guard the condition variable. This
/// should be locked by the current thread.
///
/// # Errors
/// - [`Error::MutexLock`] if another thread has the [`Mutex`] lock.
/// - [`Error::Invalid`] if self is invalid
///
/// # Examples
///
/// ```
/// use mc_sgx_tstdc::{Mutex, Condvar};
///
/// let pair = (Mutex::new(), Condvar::new());
///
/// // Wait for the thread to start up.
/// let (mutex, cvar) = &*pair;
/// // mutex must be locked prior to waiting
/// mutex.lock().unwrap();
/// // wait with the mutex. The mutex will be unlocked and relocked inside
/// // of `wait()` as needed while `cvar` is waiting.
/// cvar.wait(mutex).unwrap();
/// // cvar done waiting, mutex is again locked by this thread.
/// mutex.unlock();
///
/// // do stuff
///
/// ```
pub fn wait(&self, mutex: &Mutex) -> Result<()> {
let result = unsafe { sgx_thread_cond_wait(self.0.get(), mutex.raw()) };
match result {
0 => Ok(()),
libc::EPERM => Err(Error::MutexLock),
_ => Err(Error::Invalid),
}
}

/// Notifies the next thread (if any) waiting on the condition variable
///
/// If there are no threads waiting ([`Condvar::wait()`]) on the condition
/// variable nothing happens.
///
/// # Errors
/// [`Error::Invalid`] if self is invalid
pub fn notify_one(&self) -> Result<()> {
let result = unsafe { sgx_thread_cond_signal(self.0.get()) };
match result {
0 => Ok(()),
_ => Err(Error::Invalid),
}
}

/// Notifies *all* threads (if any) waiting on the condition variable
///
/// If there are no threads waiting ([`Condvar::wait()`]) on the condition
/// variable nothing happens.
///
/// # Errors
/// - [`Error::NoMemory`] if out of memory occurs when notifying all waiting
/// threads
/// - [`Error::Invalid`] if self is invalid
pub fn notify_all(&self) -> Result<()> {
let result = unsafe { sgx_thread_cond_broadcast(self.0.get()) };
match result {
0 => Ok(()),
libc::ENOMEM => Err(Error::NoMemory),
_ => Err(Error::Invalid),
}
}
}

impl Drop for Condvar {
fn drop(&mut self) {
let result = unsafe { sgx_thread_cond_destroy(self.0.get()) };
// There is no good way to recover from failing to destroy the condition
// variable so we leak it in release
debug_assert_eq!(result, 0);
}
}
2 changes: 2 additions & 0 deletions tstdc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
#![deny(missing_docs, missing_debug_implementations)]
#![no_std]

mod condvar;
mod mutex;
pub use condvar::{Condvar, Error as CondvarError};
pub use mutex::{Error as MutexError, Mutex};
8 changes: 8 additions & 0 deletions tstdc/src/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ impl Mutex {
_ => Err(Error::Invalid),
}
}

/// Get at the underlying SGX mutex primitive.
///
/// Callers are responsible for ensuring the returned value is *not* used
/// after the original [`Mutex`]'s lifetime.
pub(crate) unsafe fn raw(&self) -> *mut sgx_thread_mutex_t {
self.0.get()
}
}

impl Drop for Mutex {
Expand Down

0 comments on commit 655298e

Please sign in to comment.