Skip to content

Commit

Permalink
Merge pull request #138 from Zildj1an/document_locking
Browse files Browse the repository at this point in the history
docs: Document SpinLock and RWLock
  • Loading branch information
joergroedel authored Nov 20, 2023
2 parents dde1f98 + 130c1d7 commit 5fdabd7
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 0 deletions.
105 changes: 105 additions & 0 deletions src/locking/rwlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,79 +9,163 @@ 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 svsm::locking::RWLock;
///
/// #[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 +179,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 +200,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 +228,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 obtained from a [`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;
///
/// 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. That is, it allows the data to be accessed/modified
/// while enforcing 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 5fdabd7

Please sign in to comment.