lock ordering

This commit is contained in:
Jeremy Soller
2025-09-22 07:48:48 -06:00
parent e7358e3e5b
commit 5dc6f7c3ba
79 changed files with 2420 additions and 1181 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
pub use self::{wait_condition::WaitCondition, wait_queue::WaitQueue};
pub use self::{ordered::*, wait_condition::WaitCondition, wait_queue::WaitQueue};
pub mod ordered;
pub mod wait_condition;
pub mod wait_queue;
+440
View File
@@ -0,0 +1,440 @@
// This code was adapted from MIT licensed https://github.com/antialize/ordered-locks
// We cannot use that library directly as it is wrapping std::sync types
//! This create implement compiletime ordering of locks into levels, [`L1`], [`L2`], [`L3`], [`L4`] and [`L5`].
//! In order to acquire a lock at level `i` only locks at level `i-1` or below may be held.
//!
//! If locks are alwayes acquired in level order on all threads, then one cannot have a deadlock
//! involving only acquireng locks.
//!
//! In the following example we create two [muteces](Mutex) at level [`L1`] and [`L2`] and lock them
//! in the propper order.
//! ```
//! use ordered_locks::{L1, L2, Mutex, CleanLockToken};
//! // Create value at lock level 0, this lock cannot be acquired while a level1 lock is heldt
//! let v1 = Mutex::<L1, _>::new(42);
//! // Create value at lock level 1
//! let v2 = Mutex::<L2, _>::new(43);
//! // Construct a token indicating that this thread does not hold any locks
//! let mut token = unsafe {CleanLockToken::new()};
//!
//! {
//! // We can aquire the locks for v1 and v2 at the same time
//! let mut g1 = v1.lock(token.token());
//! let (g1, token) = g1.token_split();
//! let mut g2 = v2.lock(token);
//! *g2 = 11;
//! *g1 = 12;
//! }
//! // Once the guards are dropped we can acquire other things
//! *v2.lock(token.token()) = 13;
//! ```
//!
//! In the following example we create two [muteces](Mutex) at level [`L1`] and [`L2`] and try to lock
//! the mutex at [`L1`] while already holding a [`Mutex`] at [`L2`] which failes to compile.
//! ```compile_fail
//! use ordered_locks::{L1, L2, Mutex, CleanLockToken};
//! // Create value at lock level 0, this lock cannot be acquired while a level1 lock is heldt
//! let v1 = Mutex::<L1, _>::new(42);
//! // Create value at lock level 1
//! let v2 = Mutex::<L2, _>::new(43);
//! // Construct a token indicating that this thread does not hold any locks
//! let mut clean_token = unsafe {CleanLockToken::new()};
//! let token = clean_token.token();
//!
//! // Try to aquire locks in the wrong order
//! let mut g2 = v2.lock(token);
//! let (g2, token) = g2.token_split();
//! let mut g1 = v1.lock(token); // shouldn't compile!
//! *g2 = 11;
//! *g1 = 12;
//! ```
use alloc::sync::Arc;
use core::marker::PhantomData;
/// Lock level of a mutex
///
/// While a mutex of L1 is locked on a thread, only mutexes of L2 or higher may be locked.
/// This lock hierarchy prevents deadlocks from occurring. For a dead lock to occour
/// We need some thread TA to hold a resource RA, and request a resource RB, while
/// another thread TB holds RB, and requests RA. This is not possible with a lock
/// hierarchy either RA or RB must be on a level that the other.
///
/// At some point in time we would want Level to be replaced by usize, however
/// with current cont generics (rust 1.55), we cannot compare const generic arguments
/// so we are left with this mess.
pub trait Level {}
/// Indicate that the implementor is lower that the level O
pub trait Lower<O: Level>: Level {}
/// Lowest locking level, no locks can be on this level
#[derive(Debug)]
pub struct L0 {}
#[derive(Debug)]
pub struct L1 {}
#[derive(Debug)]
pub struct L2 {}
#[derive(Debug)]
pub struct L3 {}
#[derive(Debug)]
pub struct L4 {}
#[derive(Debug)]
pub struct L5 {}
impl Level for L0 {}
impl Level for L1 {}
impl Level for L2 {}
impl Level for L3 {}
impl Level for L4 {}
impl Level for L5 {}
impl Lower<L1> for L0 {}
impl Lower<L2> for L0 {}
impl Lower<L3> for L0 {}
impl Lower<L4> for L0 {}
impl Lower<L5> for L0 {}
impl Lower<L2> for L1 {}
impl Lower<L3> for L1 {}
impl Lower<L4> for L1 {}
impl Lower<L5> for L1 {}
impl Lower<L3> for L2 {}
impl Lower<L4> for L2 {}
impl Lower<L5> for L2 {}
impl Lower<L4> for L3 {}
impl Lower<L5> for L3 {}
impl Lower<L5> for L4 {}
/// Indicate that the implementor is higher that the level O
pub trait Higher<O: Level>: Level {}
impl<L1: Level, L2: Level> Higher<L2> for L1 where L2: Lower<L1> {}
/// While this exists only locks with a level higher than L, may be locked.
/// These tokens are carried around the call stack to indicate tho current locking level.
/// They have no size and should disappear at runtime.
pub struct LockToken<'a, L: Level>(PhantomData<&'a mut L>);
impl<'a, L: Level> LockToken<'a, L> {
/// Create a borrowed copy of self
pub fn token(&mut self) -> LockToken<'_, L> {
LockToken(Default::default())
}
/// Create a borrowed copy of self, on a higher level
pub fn downgrade<LC: Higher<L>>(&mut self) -> LockToken<'_, LC> {
LockToken(Default::default())
}
pub fn downgraded<LP: Lower<L>>(_: LockToken<'a, LP>) -> Self {
LockToken(Default::default())
}
}
/// Token indicating that there are no acquired locks while not borrowed.
pub struct CleanLockToken(());
impl CleanLockToken {
/// Create a borrowed copy of self
pub fn token(&mut self) -> LockToken<'_, L0> {
LockToken(Default::default())
}
/// Create a borrowed copy of self, on a higher level
pub fn downgrade<L: Level>(&mut self) -> LockToken<'_, L> {
LockToken(Default::default())
}
/// Create a new instance
///
/// # Safety
///
/// This is safe to call as long as there are no currently acquired locks
/// in the thread/task, and as long as there are no other CleanLockToken
/// in the thread/task.
///
/// A CleanLockToken
pub unsafe fn new() -> Self {
CleanLockToken(())
}
}
/// A mutual exclusion primitive useful for protecting shared data
///
/// This mutex will block threads waiting for the lock to become available. The
/// mutex can also be statically initialized or 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.
#[derive(Debug)]
pub struct Mutex<L: Level, T> {
inner: spin::Mutex<T>,
_phantom: PhantomData<L>,
}
impl<L: Level, T: Default> Default for Mutex<L, T> {
fn default() -> Self {
Self {
inner: Default::default(),
_phantom: Default::default(),
}
}
}
impl<L: Level, T> Mutex<L, T> {
/// Creates a new mutex in an unlocked state ready for use
pub const fn new(val: T) -> Self {
Self {
inner: spin::Mutex::new(val),
_phantom: PhantomData,
}
}
/// 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 mutex 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.
pub fn lock<'a, LP: Lower<L> + 'a>(
&'a self,
lock_token: LockToken<'a, LP>,
) -> MutexGuard<'a, L, T> {
MutexGuard {
inner: self.inner.lock(),
lock_token: LockToken::downgraded(lock_token),
}
}
/// Attempts to acquire this lock.
///
/// If the lock could not be acquired at this time, then `None` is returned.
/// Otherwise, an RAII guard is returned. The lock will be unlocked when the
/// guard is dropped.
///
/// This function does not block.
pub fn try_lock<'a, LP: Lower<L> + 'a>(
&'a self,
lock_token: LockToken<'a, LP>,
) -> Option<MutexGuard<'a, L, T>> {
match self.inner.try_lock() {
Some(inner) => Some(MutexGuard {
inner,
lock_token: LockToken::downgraded(lock_token),
}),
None => None,
}
}
/// Consumes this Mutex, returning the underlying data.
pub fn into_inner(self) -> T {
self.inner.into_inner()
}
}
/// 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.
pub struct MutexGuard<'a, L: Level, T: ?Sized + 'a> {
inner: spin::MutexGuard<'a, T>,
lock_token: LockToken<'a, L>,
}
impl<'a, L: Level, T: ?Sized + 'a> MutexGuard<'a, L, T> {
/// Split the guard into two parts, the first a mutable reference to the held content
/// the second a [`LockToken`] that can be used for further locking
pub fn token_split(&mut self) -> (&mut T, LockToken<'_, L>) {
(&mut self.inner, self.lock_token.token())
}
}
impl<'a, L: Level, T: ?Sized + 'a> core::ops::Deref for MutexGuard<'a, L, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
impl<'a, L: Level, T: ?Sized + 'a> core::ops::DerefMut for MutexGuard<'a, L, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.deref_mut()
}
}
pub struct RwLock<L: Level, T> {
inner: spin::RwLock<T>,
_phantom: PhantomData<L>,
}
impl<L: Level, T: Default> Default for RwLock<L, T> {
fn default() -> Self {
Self {
inner: Default::default(),
_phantom: Default::default(),
}
}
}
/// A reader-writer lock
///
/// This type of lock allows a number of readers or at most one writer at any point in time.
/// The write portion of this lock typically allows modification of the underlying data (exclusive access)
/// and the read portion of this lock typically allows for read-only access (shared access).
///
/// The type parameter T represents the data that this lock protects. It is required that T satisfies
/// Send to be shared across threads and Sync to allow concurrent access through readers.
/// The RAII guards returned from the locking methods implement Deref (and DerefMut for the write methods)
/// to allow access to the contained of the lock.
impl<L: Level, T> RwLock<L, T> {
/// Creates a new instance of an RwLock<T> which is unlocked.
pub const fn new(val: T) -> Self {
Self {
inner: spin::RwLock::new(val),
_phantom: PhantomData,
}
}
/// Consumes this RwLock, returning the underlying data.
pub fn into_inner(self) -> T {
self.inner.into_inner()
}
/// Locks this RwLock with exclusive write access, blocking the current thread until it can be acquired.
/// This function will not return while other writers or other readers currently have access to the lock.
/// Returns an RAII guard which will drop the write access of this RwLock when dropped.
pub fn write<'a, LP: Lower<L> + 'a>(
&'a self,
lock_token: LockToken<'a, LP>,
) -> RwLockWriteGuard<'a, L, T> {
RwLockWriteGuard {
inner: self.inner.write(),
lock_token: LockToken::downgraded(lock_token),
}
}
/// Locks this RwLock with shared read access, blocking the current thread until it can be acquired.
///
/// The calling thread will be blocked until there are no more writers which hold the lock.
/// There may be other readers currently inside the lock when this method returns.
///
/// Note that attempts to recursively acquire a read lock on a RwLock when the current thread
/// already holds one may result in a deadlock.
///
/// Returns an RAII guard which will release this threads shared access once it is dropped.
pub fn read<'a, LP: Lower<L> + 'a>(
&'a self,
lock_token: LockToken<'a, LP>,
) -> RwLockReadGuard<'a, L, T> {
RwLockReadGuard {
inner: self.inner.read(),
lock_token: LockToken::downgraded(lock_token),
}
}
// Unsafe due to not using token, currently required by context::switch
pub unsafe fn write_arc(self: &Arc<Self>) -> ArcRwLockWriteGuard<L, T> {
core::mem::forget(self.inner.write());
ArcRwLockWriteGuard {
rwlock: self.clone(),
}
}
}
/// RAII structure used to release the exclusive write access of a lock when dropped
pub struct RwLockWriteGuard<'a, L: Level, T> {
inner: spin::RwLockWriteGuard<'a, T>,
lock_token: LockToken<'a, L>,
}
impl<'a, L: Level, T> RwLockWriteGuard<'a, L, T> {
/// Split the guard into two parts, the first a mutable reference to the held content
/// the second a [`LockToken`] that can be used for further locking
pub fn token_split(&mut self) -> (&mut T, LockToken<'_, L>) {
(&mut self.inner, self.lock_token.token())
}
}
impl<'a, L: Level, T> core::ops::Deref for RwLockWriteGuard<'a, L, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
impl<'a, L: Level, T> core::ops::DerefMut for RwLockWriteGuard<'a, L, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.deref_mut()
}
}
/// RAII structure used to release the shared read access of a lock when dropped.
pub struct RwLockReadGuard<'a, L: Level, T> {
inner: spin::RwLockReadGuard<'a, T>,
lock_token: LockToken<'a, L>,
}
impl<'a, L: Level, T> RwLockReadGuard<'a, L, T> {
/// Split the guard into two parts, the first a reference to the held content
/// the second a [`LockToken`] that can be used for further locking
pub fn token_split(&mut self) -> (&T, LockToken<'_, L>) {
(&self.inner, self.lock_token.token())
}
}
impl<'a, L: Level, T> core::ops::Deref for RwLockReadGuard<'a, L, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
pub struct ArcRwLockWriteGuard<L: Level + 'static, T> {
rwlock: Arc<RwLock<L, T>>,
}
impl<L: Level, T> ArcRwLockWriteGuard<L, T> {
pub fn rwlock(s: &Self) -> &Arc<RwLock<L, T>> {
&s.rwlock
}
}
impl<L: Level, T> core::ops::Deref for ArcRwLockWriteGuard<L, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &*self.rwlock.inner.as_mut_ptr() }
}
}
impl<L: Level, T> core::ops::DerefMut for ArcRwLockWriteGuard<L, T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.rwlock.inner.as_mut_ptr() }
}
}
impl<L: Level, T> Drop for ArcRwLockWriteGuard<L, T> {
#[inline]
fn drop(&mut self) {
unsafe {
self.rwlock.inner.force_write_unlock();
}
}
}
/// This function can only be called if no lock is held by the calling thread/task
#[inline]
pub fn check_no_locks(_: LockToken<'_, L0>) {}
+23 -16
View File
@@ -2,14 +2,15 @@ use alloc::{
sync::{Arc, Weak},
vec::Vec,
};
use spin::Mutex;
use spinning_top::RwSpinlock;
use crate::context::{self, Context};
use crate::{
context::{self, ContextLock},
sync::{CleanLockToken, Mutex, L1},
};
#[derive(Debug)]
pub struct WaitCondition {
contexts: Mutex<Vec<Weak<RwSpinlock<Context>>>>,
contexts: Mutex<L1, Vec<Weak<ContextLock>>>,
}
impl WaitCondition {
@@ -20,35 +21,37 @@ impl WaitCondition {
}
// Notify all waiters
pub fn notify(&self) -> usize {
let mut contexts = self.contexts.lock();
pub fn notify(&self, token: &mut CleanLockToken) -> usize {
let mut contexts = self.contexts.lock(token.token());
let (contexts, mut token) = contexts.token_split();
let len = contexts.len();
while let Some(context_weak) = contexts.pop() {
if let Some(context_ref) = context_weak.upgrade() {
context_ref.write().unblock();
context_ref.write(token.token()).unblock();
}
}
len
}
// Notify as though a signal woke the waiters
pub unsafe fn notify_signal(&self) -> usize {
let contexts = self.contexts.lock();
pub unsafe fn notify_signal(&self, token: &mut CleanLockToken) -> usize {
let mut contexts = self.contexts.lock(token.token());
let (contexts, mut token) = contexts.token_split();
let len = contexts.len();
for context_weak in contexts.iter() {
if let Some(context_ref) = context_weak.upgrade() {
context_ref.write().unblock();
context_ref.write(token.token()).unblock();
}
}
len
}
// Wait until notified. Unlocks guard when blocking is ready. Returns false if resumed by a signal or the notify_signal function
pub fn wait<T>(&self, guard: T, reason: &'static str) -> bool {
pub fn wait<T>(&self, guard: T, reason: &'static str, token: &mut CleanLockToken) -> bool {
let current_context_ref = context::current();
{
{
let mut context = current_context_ref.write();
let mut context = current_context_ref.write(token.token());
if let Some((control, pctl, _)) = context.sigcontrol()
&& control.currently_pending_unblocked(pctl) != 0
{
@@ -58,18 +61,18 @@ impl WaitCondition {
}
self.contexts
.lock()
.lock(token.token())
.push(Arc::downgrade(&current_context_ref));
drop(guard);
}
context::switch();
context::switch(token);
let mut waited = true;
{
let mut contexts = self.contexts.lock();
let mut contexts = self.contexts.lock(token.token());
// TODO: retain
let mut i = 0;
@@ -90,6 +93,10 @@ impl WaitCondition {
impl Drop for WaitCondition {
fn drop(&mut self) {
unsafe { self.notify_signal() };
//TODO: drop violates lock tokens
unsafe {
let mut token = CleanLockToken::new();
self.notify_signal(&mut token);
};
}
}
+12 -6
View File
@@ -3,7 +3,7 @@ use spin::Mutex;
use syscall::{EAGAIN, EINTR};
use crate::{
sync::WaitCondition,
sync::{CleanLockToken, WaitCondition},
syscall::{
error::{Error, Result, EINVAL},
usercopy::UserSliceWo,
@@ -27,7 +27,12 @@ impl<T> WaitQueue<T> {
self.inner.lock().is_empty()
}
pub fn receive(&self, block: bool, reason: &'static str) -> Result<T> {
pub fn receive(
&self,
block: bool,
reason: &'static str,
token: &mut CleanLockToken,
) -> Result<T> {
loop {
let mut inner = self.inner.lock();
@@ -37,7 +42,7 @@ impl<T> WaitQueue<T> {
}
_ => {
if block {
if !self.condition.wait(inner, reason) {
if !self.condition.wait(inner, reason, token) {
return Err(Error::new(EINTR));
}
continue;
@@ -54,13 +59,14 @@ impl<T> WaitQueue<T> {
buf: UserSliceWo,
block: bool,
reason: &'static str,
token: &mut CleanLockToken,
) -> Result<usize> {
loop {
let mut inner = self.inner.lock();
if inner.is_empty() {
if block {
if !self.condition.wait(inner, reason) {
if !self.condition.wait(inner, reason, token) {
return Err(Error::new(EINTR));
}
continue;
@@ -100,13 +106,13 @@ impl<T> WaitQueue<T> {
}
}
pub fn send(&self, value: T) -> usize {
pub fn send(&self, value: T, token: &mut CleanLockToken) -> usize {
let len = {
let mut inner = self.inner.lock();
inner.push_back(value);
inner.len()
};
self.condition.notify();
self.condition.notify(token);
len
}
}