From ff6b8b7e8dc396eeb4e219aaed7331a0f1a80cf9 Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Mon, 1 Jun 2026 01:04:20 -0700 Subject: [PATCH] Round out docs --- README.md | 45 ++++++++++++++++++++++++++++++ src/lib.rs | 45 ++++++++++++++++++++++++++++++ src/locksmith.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ src/mutex.rs | 38 +++++++++++++++++++++++-- src/rw_lock.rs | 38 +++++++++++++++++++++++-- 5 files changed, 232 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 08efbc6..ee0a568 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ handle.scope(|key| { }); ``` +> For everyday use, `use surelock::prelude::*;` pulls in the common +> types, traits, and entry points (locks, `KeyHandle`, `LockSet`, +> levels, the `NewHigher` traits, and — on `std` — `lock_scope`), so +> you don't need to import from each module individually. + ## Type Lifecycle ```text @@ -193,6 +198,46 @@ single `LockSet` can hold both: let set = LockSet::new((&mtx, ReadLock(&rwlock))); ``` +### Non-Blocking Acquisition + +When you must not stall (real-time paths, poll-don't-wait loops, work +stealing), the `try_*` methods acquire without blocking. On success you +get the guard plus a key advanced to the lock's level; on contention +you get the **original key back, unchanged**, so you can retry or do +other work: + +```rust +use surelock::{key::lock_scope, mutex::Mutex}; + +let queue: Mutex = Mutex::new(0); + +lock_scope(|key| { + match key.try_lock(&queue) { + Ok((mut guard, _key)) => *guard += 1, // acquired + Err(_key) => { /* busy -- retry, or do other work */ } + } +}); +``` + +`try_read` / `try_write` are the `RwLock` counterparts, and +`try_lock_set` acquires a whole `LockSet` **all-or-nothing** (if any +lock is contended, none is left held): + +```rust +use surelock::{key::lock_scope, mutex::Mutex, set::LockSet}; + +let a: Mutex = Mutex::new(1); +let b: Mutex = Mutex::new(2); +let set = LockSet::new((&a, &b)); + +lock_scope(|key| { + match key.try_lock_set(&set) { + Ok(((ga, gb), _key)) => assert_eq!(*ga + *gb, 3), + Err(_key) => { /* at least one lock was busy; none held */ } + } +}); +``` + ### Key Patterns ```rust diff --git a/src/lib.rs b/src/lib.rs index f2234d4..6918710 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,6 +165,51 @@ //! }); //! ``` //! +//! ## Non-Blocking Acquisition +//! +//! When you must not stall (real-time paths, poll-don't-wait loops, +//! work stealing), use the `try_*` methods. They never block: on +//! success you get the guard plus a key advanced to the lock's level +//! (exactly as the blocking forms do); on contention you get the +//! **original key back, unchanged**, so you can retry, take another +//! path, or do other work. +//! +//! ```rust +//! use surelock::{key::lock_scope, mutex::Mutex}; +//! +//! let queue: Mutex = Mutex::new(0); +//! +//! lock_scope(|key| { +//! match key.try_lock(&queue) { +//! Ok((mut guard, _key)) => *guard += 1, // acquired +//! Err(_key) => { /* busy -- retry, or do other work */ } +//! } +//! }); +//! ``` +//! +//! [`try_read`](crate::key::MutexKey::try_read) / +//! [`try_write`](crate::key::MutexKey::try_write) are the +//! [`RwLock`](crate::rw_lock::RwLock) counterparts. +//! [`try_lock_set`](crate::key::MutexKey::try_lock_set) acquires a +//! whole [`LockSet`](crate::set::LockSet) **all-or-nothing**: if any +//! lock is contended, none is left held and the original key is +//! returned. +//! +//! ```rust +//! use surelock::{key::lock_scope, mutex::Mutex, set::LockSet}; +//! +//! let a: Mutex = Mutex::new(1); +//! let b: Mutex = Mutex::new(2); +//! let set = LockSet::new((&a, &b)); +//! +//! lock_scope(|key| { +//! match key.try_lock_set(&set) { +//! Ok(((ga, gb), _key)) => assert_eq!(*ga + *gb, 3), +//! Err(_key) => { /* at least one lock was busy; none held */ } +//! } +//! }); +//! ``` +//! //! ## Nested Scopes //! //! Use [`subscope`](crate::key::MutexKey::subscope) to create a diff --git a/src/locksmith.rs b/src/locksmith.rs index a8ab19f..bf739c7 100644 --- a/src/locksmith.rs +++ b/src/locksmith.rs @@ -111,9 +111,23 @@ impl Locksmith { /// Create a `Locksmith` with the given limit, panicking if one /// already exists. /// + /// Convenience wrapper over [`new`](Locksmith::new) for the common + /// "construct once at init" case where a pre-existing `Locksmith` + /// would be a programming error rather than a recoverable + /// condition. + /// /// # Panics /// /// Panics if a `Locksmith` already exists. + /// + /// # Examples + /// + /// ```rust + /// use surelock::locksmith::Locksmith; + /// + /// let smith = Locksmith::create(4); // 4-core system + /// assert_eq!(smith.limit(), Some(4)); + /// ``` #[must_use] #[allow(clippy::expect_used)] pub fn create(limit: usize) -> Self { @@ -123,9 +137,21 @@ impl Locksmith { /// Create an unlimited `Locksmith`, panicking if one already /// exists. /// + /// Convenience wrapper over [`unlimited`](Locksmith::unlimited); + /// see [`create`](Locksmith::create) for the rationale. + /// /// # Panics /// /// Panics if a `Locksmith` already exists. + /// + /// # Examples + /// + /// ```rust + /// use surelock::locksmith::Locksmith; + /// + /// let smith = Locksmith::create_unlimited(); + /// assert_eq!(smith.limit(), None); + /// ``` #[must_use] #[allow(clippy::expect_used)] pub fn create_unlimited() -> Self { @@ -165,18 +191,64 @@ impl Locksmith { } /// Number of vouchers issued so far. + /// + /// This counter only ever increments -- dropping a [`KeyVoucher`] + /// does not decrease it (see [`remaining`](Locksmith::remaining)). + /// + /// # Examples + /// + /// ```rust + /// use surelock::locksmith::Locksmith; + /// + /// let smith = Locksmith::new(3).unwrap(); + /// assert_eq!(smith.issued(), 0); + /// let _v = smith.issue().unwrap(); + /// assert_eq!(smith.issued(), 1); + /// ``` #[must_use] pub fn issued(&self) -> usize { self.issued.load(Ordering::Relaxed) } /// The configured limit, or `None` if unlimited. + /// + /// # Examples + /// + /// ```rust + /// use surelock::locksmith::Locksmith; + /// + /// assert_eq!(Locksmith::new(4).unwrap().limit(), Some(4)); + /// ``` #[must_use] pub const fn limit(&self) -> Option { self.limit } /// Remaining vouchers that can be issued, or `None` if unlimited. + /// + /// Because the issuance counter only increments, **dropping a + /// voucher does not free a slot** -- `remaining` reflects the + /// number *issued*, not the number currently live. + /// + /// # Examples + /// + /// ```rust + /// use surelock::locksmith::Locksmith; + /// + /// let smith = Locksmith::new(2).unwrap(); + /// assert_eq!(smith.remaining(), Some(2)); + /// + /// let voucher = smith.issue().unwrap(); + /// assert_eq!(smith.remaining(), Some(1)); + /// + /// // Dropping the voucher does NOT restore the slot. + /// drop(voucher); + /// assert_eq!(smith.remaining(), Some(1)); + /// + /// // An unlimited Locksmith has no finite remaining count. + /// drop(smith); + /// assert_eq!(Locksmith::unlimited().unwrap().remaining(), None); + /// ``` #[must_use] pub fn remaining(&self) -> Option { self.limit.map(|l| l.saturating_sub(self.issued())) diff --git a/src/mutex.rs b/src/mutex.rs index 8a65cb7..82d9de3 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -92,6 +92,14 @@ impl Mutex { /// Create a new mutex with the given data. /// /// A unique [`LockId`] is assigned from a global atomic counter. + /// + /// # Examples + /// + /// ```rust + /// use surelock::mutex::Mutex; + /// + /// let counter: Mutex = Mutex::new(0); + /// ``` #[must_use] pub fn new(data: T) -> Self { Self { @@ -108,16 +116,40 @@ impl Mutex { self.id } - /// Exclusive access via `&mut` -- no locking needed. + /// Exclusive access via `&mut` -- no locking (and no + /// [`KeyHandle`](crate::key_handle::KeyHandle)) needed. + /// + /// Since the caller has exclusive ownership (`&mut self`), no other + /// thread can be holding the lock, so access is sound without + /// entering a scope. + /// + /// # Examples + /// + /// ```rust + /// use surelock::mutex::Mutex; /// - /// Since the caller has exclusive ownership, no other thread can - /// be holding the lock. + /// let mut m: Mutex = Mutex::new(1); + /// *m.get_mut() += 41; // exclusive access, no key required + /// assert_eq!(m.into_inner(), 42); + /// ``` #[must_use] pub const fn get_mut(&mut self) -> &mut T { self.data.get_mut() } /// Consume the mutex and return the inner data. + /// + /// No [`KeyHandle`](crate::key_handle::KeyHandle) is needed -- + /// consuming the mutex proves no guard is outstanding. + /// + /// # Examples + /// + /// ```rust + /// use surelock::mutex::Mutex; + /// + /// let m: Mutex> = Mutex::new(vec![1, 2, 3]); + /// assert_eq!(m.into_inner(), vec![1, 2, 3]); + /// ``` #[must_use] pub fn into_inner(self) -> T { self.data.into_inner() diff --git a/src/rw_lock.rs b/src/rw_lock.rs index 499583c..969c80d 100644 --- a/src/rw_lock.rs +++ b/src/rw_lock.rs @@ -127,6 +127,14 @@ impl RwLock { /// [`RwLock`] and a [`Mutex`](crate::mutex::Mutex) can sit side /// by side in a single [`LockSet`](crate::set::LockSet) and sort /// deterministically. + /// + /// # Examples + /// + /// ```rust + /// use surelock::rw_lock::RwLock; + /// + /// let cache: RwLock = RwLock::new(0); + /// ``` #[must_use] pub fn new(data: T) -> Self { Self { @@ -143,16 +151,40 @@ impl RwLock { self.id } - /// Exclusive access via `&mut` -- no locking needed. + /// Exclusive access via `&mut` -- no locking (and no + /// [`KeyHandle`](crate::key_handle::KeyHandle)) needed. + /// + /// Since the caller has exclusive ownership (`&mut self`), no other + /// thread can be holding the lock, so access is sound without + /// entering a scope. /// - /// Since the caller has exclusive ownership, no other thread can - /// be holding the lock. + /// # Examples + /// + /// ```rust + /// use surelock::rw_lock::RwLock; + /// + /// let mut rw: RwLock = RwLock::new(1); + /// *rw.get_mut() += 41; // exclusive access, no key required + /// assert_eq!(rw.into_inner(), 42); + /// ``` #[must_use] pub const fn get_mut(&mut self) -> &mut T { self.data.get_mut() } /// Consume the rwlock and return the inner data. + /// + /// No [`KeyHandle`](crate::key_handle::KeyHandle) is needed -- + /// consuming the rwlock proves no guard is outstanding. + /// + /// # Examples + /// + /// ```rust + /// use surelock::rw_lock::RwLock; + /// + /// let rw: RwLock = RwLock::new(42); + /// assert_eq!(rw.into_inner(), 42); + /// ``` #[must_use] pub fn into_inner(self) -> T { self.data.into_inner() -- 2.51.2