surelock 0.1.0

Deadlock-free locks for Rust with compile time guarantees, incremental locks, and atomic lock sets.
Documentation
//! Per-thread capability for lock scope creation. See [`KeyHandle`].

use core::marker::PhantomData;

use crate::{key::MutexKey, level::Bottom};

/// Sentinel type stored in `thread_local!`. Its presence means
/// no handle has been claimed on this thread.
#[cfg(feature = "std")]
struct KeyHandleSlot;

#[cfg(feature = "std")]
mod storage {
    extern crate std;

    use core::cell::Cell;

    use super::KeyHandleSlot;

    std::thread_local! {
        static SLOT: Cell<Option<KeyHandleSlot>> =
            const { Cell::new(Some(KeyHandleSlot)) };
    }

    /// Take the handle slot from thread-local storage.
    /// Returns `Some` if available (first claim), `None` if already taken.
    pub(crate) fn take() -> Option<KeyHandleSlot> {
        SLOT.with(Cell::take)
    }

    /// Put the handle slot back into thread-local storage.
    pub(crate) fn put_back() {
        SLOT.with(|cell| {
            cell.set(Some(KeyHandleSlot));
        });
    }
}

/// Per-thread capability to create lock scopes.
///
/// The root of authority for lock acquisition on its thread.
/// Claimed once per thread via [`try_claim`](KeyHandle::try_claim)
/// or [`claim`](KeyHandle::claim), and provides
/// [`scope`](KeyHandle::scope) for creating [`MutexKey`]s inside a
/// branded-lifetime closure.
///
/// `scope(&mut self)` takes a mutable borrow, so the borrow checker
/// prevents nesting at compile time -- no runtime check needed. This
/// is the static alternative to [`try_lock_scope`](crate::key::try_lock_scope)
/// / [`lock_scope`](crate::key::lock_scope) (which are `std`-only
/// and use runtime checks).
///
/// `!Clone`, `!Copy`, `!Send`, `!Sync`.
///
/// # Per-Thread, Not Global
///
/// Each thread claims its own handle. Cross-thread deadlock prevention
/// comes from [`LockSet`](crate::set::LockSet)'s sorted acquisition
/// and [`LockAfter`](crate::level::LockAfter)'s level ordering --
/// those are properties of the locks, not the handle.
///
/// # Storage Model
///
/// On `std`, each thread has a `KeyHandle` pre-allocated in
/// `thread_local!` storage. [`try_claim`](KeyHandle::try_claim) takes
/// it out (`.take()`), and [`Drop`] puts it back. This ensures at
/// most one active handle per thread by construction.
///
/// On `no_std`, `try_claim` always succeeds (no `thread_local!`).
/// This is **dangerous** -- multiple handles can coexist, creating
/// independent keys that defeat the ordering guarantee. On
/// `no_std`, use [`Locksmith`](crate::locksmith::Locksmith) to
/// distribute handles explicitly, originating from a single root
/// of authority. The `&mut self` on `scope` prevents nesting
/// within a single handle but not across multiple handles.
///
/// # Examples
///
/// ```rust
/// use surelock::{key_handle::KeyHandle, mutex::Mutex};
///
/// let mut handle = KeyHandle::claim();
///
/// handle.scope(|key| {
///     let m: Mutex<u32> = Mutex::new(42);
///     let (guard, _key) = key.lock(&m);
///     assert_eq!(*guard, 42);
/// });
///
/// // Sequential scopes are fine
/// handle.scope(|key| {
///     // fresh scope, new key at Bottom
/// });
///
/// // Nesting is a compile error:
/// // handle.scope(|key1| {
/// //     handle.scope(|key2| { ... });
/// //     ^^^^ error: already mutably borrowed
/// // });
/// ```
pub struct KeyHandle {
    _not_send: PhantomData<*const ()>,
}

impl KeyHandle {
    /// Try to claim the `KeyHandle` for the current thread.
    ///
    /// On `std`: takes the handle from `thread_local!` storage.
    /// Returns `None` if already claimed (by a previous `try_claim`
    /// or an active `try_lock_scope` / `lock_scope`).
    ///
    /// On `no_std`: always returns `Some` (no `thread_local!`
    /// available for uniqueness checking). This is **dangerous** --
    /// multiple handles on the same execution context can create
    /// independent keys that defeat the ordering guarantee. On
    /// `no_std`, prefer using [`Locksmith`](crate::locksmith::Locksmith)
    /// to distribute `KeyHandle`s explicitly, and thread the handle
    /// via `&mut` references. The `&mut self` on
    /// [`scope`](KeyHandle::scope) prevents nesting within a single
    /// handle, but cannot prevent multiple handles from coexisting.
    #[must_use]
    #[allow(clippy::missing_const_for_fn)] // std branch calls non-const storage::take()
    pub fn try_claim() -> Option<Self> {
        #[cfg(feature = "std")]
        {
            storage::take().map(|_slot| Self {
                _not_send: PhantomData,
            })
        }

        #[cfg(not(feature = "std"))]
        {
            Some(Self {
                _not_send: PhantomData,
            })
        }
    }

    /// Claim the `KeyHandle` for the current thread, panicking if
    /// already claimed.
    ///
    /// # Panics
    ///
    /// Panics if a `KeyHandle` or active lock scope already exists
    /// on this thread.
    #[must_use]
    #[allow(clippy::expect_used, clippy::new_without_default)]
    pub fn claim() -> Self {
        Self::try_claim().expect("surelock: KeyHandle already claimed on this thread")
    }

    /// Provide a [`MutexKey`] to the closure for ordered lock acquisition.
    ///
    /// Takes `&mut self` -- the borrow checker prevents calling
    /// `scope` while already inside a `scope` (compile error, not
    /// runtime check). This is the static nesting prevention
    /// mechanism.
    ///
    /// The key starts at [`Bottom`] and advances through
    /// levels as locks are acquired.
    pub fn scope<F, Ret>(&mut self, f: F) -> Ret
    where
        F: for<'scope> FnOnce(MutexKey<'scope, Bottom>) -> Ret,
    {
        f(MutexKey::new_internal())
    }
}

impl Drop for KeyHandle {
    fn drop(&mut self) {
        #[cfg(feature = "std")]
        storage::put_back();
    }
}

impl core::fmt::Debug for KeyHandle {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("KeyHandle").finish_non_exhaustive()
    }
}