Skip to content

Commit

Permalink
docs: Document SpinLock and RWLock
Browse files Browse the repository at this point in the history
Document locking support. This is related to issue #74 Document
COCONUT-SVSM.

Signed-off-by: Carlos Bilbao <[email protected]>
  • Loading branch information
Zildj1an committed Oct 19, 2023
1 parent dde36a0 commit 8c21ba8
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 0 deletions.
107 changes: 107 additions & 0 deletions src/locking/rwlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,79 +9,165 @@ use core::fmt::Debug;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicU64, Ordering};

/// A guard that provides read access to the data protected by `RWLock`
#[derive(Debug)]
#[must_use = "if unused the RWLock will immediately unlock"]
pub struct ReadLockGuard<'a, T: Debug> {
/// Reference to the associated `AtomicU64` in the `RWLock`
rwlock: &'a AtomicU64,
/// Reference to the protected data
data: &'a T,
}

/// Implements the behavior of the `ReadLockGuard` when it is dropped
impl<'a, T: Debug> Drop for ReadLockGuard<'a, T> {
/// Release the read lock
fn drop(&mut self) {
self.rwlock.fetch_sub(1, Ordering::Release);
}
}

/// Implements the behavior of dereferencing the `ReadLockGuard` to
/// access the protected data.
impl<'a, T: Debug> Deref for ReadLockGuard<'a, T> {
type Target = T;
/// Allow reading the protected data through deref
fn deref(&self) -> &T {
self.data
}
}

/// A guard that provides exclusive write access to the data protected by `RWLock`
#[derive(Debug)]
#[must_use = "if unused the RWLock will immediately unlock"]
pub struct WriteLockGuard<'a, T: Debug> {
/// Reference to the associated `AtomicU64` in the `RWLock`
rwlock: &'a AtomicU64,
/// Reference to the protected data (mutable)
data: &'a mut T,
}

/// Implements the behavior of the `WriteLockGuard` when it is dropped
impl<'a, T: Debug> Drop for WriteLockGuard<'a, T> {
fn drop(&mut self) {
// There are no readers - safe to just set lock to 0
self.rwlock.store(0, Ordering::Release);
}
}

/// Implements the behavior of dereferencing the `WriteLockGuard` to
/// access the protected data.
impl<'a, T: Debug> Deref for WriteLockGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.data
}
}

/// Implements the behavior of dereferencing the `WriteLockGuard` to
/// access the protected data in a mutable way.
impl<'a, T: Debug> DerefMut for WriteLockGuard<'a, T> {
fn deref_mut(&mut self) -> &mut T {
self.data
}
}

/// A simple Read-Write Lock (RWLock) that allows multiple readers or
/// one exclusive writer.
#[derive(Debug)]
pub struct RWLock<T: Debug> {
/// An atomic 64-bit integer used for synchronization
rwlock: AtomicU64,
/// An UnsafeCell for interior mutability
data: UnsafeCell<T>,
}

/// Implements the trait `Sync` for the `RWLock`, allowing safe
/// concurrent access across threads.
unsafe impl<T: Debug> Sync for RWLock<T> {}

/// Splits a 64-bit value into two parts: readers (low 32 bits) and
/// writers (high 32 bits).
///
/// # Parameters
///
/// - `val`: A 64-bit unsigned integer value to be split.
///
/// # Returns
///
/// A tuple containing two 32-bit unsigned integer values. The first
/// element of the tuple is the lower 32 bits of input value, and the
/// second is the upper 32 bits.
///
#[inline]
fn split_val(val: u64) -> (u64, u64) {
(val & 0xffff_ffffu64, val >> 32)
}

/// Composes a 64-bit value by combining the number of readers (low 32
/// bits) and writers (high 32 bits). This function is used to create a
/// 64-bit synchronization value that represents the current state of the
/// RWLock, including the count of readers and writers.
///
/// # Parameters
///
/// - `readers`: The number of readers (low 32 bits) currently holding read locks.
/// - `writers`: The number of writers (high 32 bits) currently holding write locks.
///
/// # Returns
///
/// A 64-bit value representing the combined state of readers and writers in the RWLock.
///
#[inline]
fn compose_val(readers: u64, writers: u64) -> u64 {
(readers & 0xffff_ffffu64) | (writers << 32)
}

/// A reader-writer lock that allows multiple readers or a single writer
/// to access the protected data. `RWLock` provides exclusive access for
/// writers and shared access for readers, for efficient synchronization.
///
impl<T: Debug> RWLock<T> {
/// Creates a new `RWLock` instance with the provided initial data.
///
/// # Parameters
///
/// - `data`: The initial data to be protected by the `RWLock`.
///
/// # Returns
///
/// A new `RWLock` instance with the specified initial data.
///
/// # Example
///
/// ```rust
/// use core::sync::atomic::{AtomicU64, Ordering};
/// use std::fmt::Debug;
/// use svsm::locking::*;
///
/// #[derive(Debug)]
/// struct MyData {
/// value: i32,
/// }
///
/// let data = MyData { value: 42 };
/// let rwlock = RWLock::new(data);
/// ```
pub const fn new(data: T) -> Self {
RWLock {
rwlock: AtomicU64::new(0),
data: UnsafeCell::new(data),
}
}

/// This function is used to wait until all writers have finished their
/// operations and retrieve the current state of the RWLock.
///
/// # Returns
///
/// A 64-bit value representing the current state of the RWLock,
/// including the count of readers and writers.
///
#[inline]
fn wait_for_writers(&self) -> u64 {
loop {
Expand All @@ -95,6 +181,14 @@ impl<T: Debug> RWLock<T> {
}
}

/// This function is used to wait until all readers have finished their
/// operations and retrieve the current state of the RWLock.
///
/// # Returns
///
/// A 64-bit value representing the current state of the RWLock,
/// including the count of readers and writers.
///
#[inline]
fn wait_for_readers(&self) -> u64 {
loop {
Expand All @@ -108,6 +202,12 @@ impl<T: Debug> RWLock<T> {
}
}

/// This function allows multiple readers to access the data concurrently.
///
/// # Returns
///
/// A `ReadLockGuard` that provides read access to the protected data.
///
pub fn lock_read(&self) -> ReadLockGuard<T> {
loop {
let val = self.wait_for_writers();
Expand All @@ -130,6 +230,13 @@ impl<T: Debug> RWLock<T> {
}
}

/// This function ensures exclusive access for a single writer and waits
/// for all readers to finish before granting access to the writer.
///
/// # Returns
///
/// A `WriteLockGuard` that provides write access to the protected data.
///
pub fn lock_write(&self) -> WriteLockGuard<T> {
// Waiting for current writer to finish
loop {
Expand Down
83 changes: 83 additions & 0 deletions src/locking/spinlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,107 @@ use core::fmt::Debug;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicU64, Ordering};

/// A lock guard used in conjunction with `SpinLock`. This lock guard
/// provides exclusive access to the data protected by a `SpinLock`,
/// ensuring that the lock is released when it goes out of scope.
///
/// # Examples
///
/// ```
/// use svsm::locking::{SpinLock, LockGuard};
///
/// let data = 42;
/// let spin_lock = SpinLock::new(data);
///
/// {
/// let mut guard = spin_lock.lock();
/// *guard += 1; // Modify the protected data.
/// }; // Lock is automatically released when `guard` goes out of scope.
/// ```
#[derive(Debug)]
#[must_use = "if unused the SpinLock will immediately unlock"]
pub struct LockGuard<'a, T: Debug> {
holder: &'a AtomicU64,
data: &'a mut T,
}

/// Implements the behavior of the `LockGuard` when it is dropped
impl<'a, T: Debug> Drop for LockGuard<'a, T> {
/// Automatically releases the lock when the guard is dropped
fn drop(&mut self) {
self.holder.fetch_add(1, Ordering::Release);
}
}

/// Implements the behavior of dereferencing the `LockGuard` to
/// access the protected data.
impl<'a, T: Debug> Deref for LockGuard<'a, T> {
type Target = T;
/// Provides read-only access to the protected data
fn deref(&self) -> &T {
self.data
}
}

/// Implements the behavior of dereferencing the `LockGuard` to
/// access the protected data in a mutable way.
impl<'a, T: Debug> DerefMut for LockGuard<'a, T> {
/// Provides mutable access to the protected data
fn deref_mut(&mut self) -> &mut T {
self.data
}
}

/// A simple spinlock implementation for protecting concurrent data access.
///
/// # Examples
///
/// ```
/// use svsm::locking::SpinLock;
///
/// let data = 42;
/// let spin_lock = SpinLock::new(data);
///
/// // Acquire the lock and modify the protected data.
/// {
/// let mut guard = spin_lock.lock();
/// *guard += 1;
/// }; // Lock is automatically released when `guard` goes out of scope.
///
/// // Try to acquire the lock without blocking
/// if let Some(mut guard) = spin_lock.try_lock() {
/// *guard += 2;
/// };
/// ```
#[derive(Debug)]
pub struct SpinLock<T: Debug> {
/// This atomic counter is incremented each time a thread attempts to
/// acquire the lock. It helps to determine the order in which threads
/// acquire the lock.
current: AtomicU64,
/// This counter represents the thread that currently holds the lock
/// and has access to the protected data.
holder: AtomicU64,
/// This `UnsafeCell` is used to provide interior mutability of the
/// protected data, this is, it allows the data to be accessed/modified
/// while respecting the locking mechanism.
data: UnsafeCell<T>,
}

unsafe impl<T: Debug + Send> Send for SpinLock<T> {}
unsafe impl<T: Debug + Send> Sync for SpinLock<T> {}

impl<T: Debug> SpinLock<T> {
/// Creates a new SpinLock instance with the specified initial data.
///
/// # Examples
///
/// ```
/// use svsm::locking::SpinLock;
///
/// let data = 42;
/// let spin_lock = SpinLock::new(data);
/// ```
pub const fn new(data: T) -> Self {
SpinLock {
current: AtomicU64::new(0),
Expand All @@ -54,6 +118,21 @@ impl<T: Debug> SpinLock<T> {
}
}

/// Acquires the lock, providing access to the protected data.
///
/// # Examples
///
/// ```
/// use svsm::locking::SpinLock;
///
/// let spin_lock = SpinLock::new(42);
///
/// // Acquire the lock and modify the protected data.
/// {
/// let mut guard = spin_lock.lock();
/// *guard += 1;
/// }; // Lock is automatically released when `guard` goes out of scope.
/// ```
pub fn lock(&self) -> LockGuard<T> {
let ticket = self.current.fetch_add(1, Ordering::Relaxed);
loop {
Expand All @@ -69,6 +148,10 @@ impl<T: Debug> SpinLock<T> {
}
}

/// This method tries to acquire the lock without blocking. If the
/// lock is not available, it returns `None`. If the lock is
/// successfully acquired, it returns a `LockGuard` that automatically
/// releases the lock when it goes out of scope.
pub fn try_lock(&self) -> Option<LockGuard<T>> {
let current = self.current.load(Ordering::Relaxed);
let holder = self.holder.load(Ordering::Acquire);
Expand Down

0 comments on commit 8c21ba8

Please sign in to comment.