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

docs: Document SpinLock and RWLock #138

Merged
merged 1 commit into from
Nov 20, 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
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
Loading