Skip to main content

noxu_sync/
lib.rs

1// Copyright (C) 2024-2025 Greg Burd.  Licensed under either of the
2// Apache License, Version 2.0 or the MIT license, at your option.
3// See LICENSE-APACHE and LICENSE-MIT at the root of this repository.
4// SPDX-License-Identifier: Apache-2.0 OR MIT
5
6//! Noxu DB synchronization primitives.
7//!
8//! Futex-based `Mutex<T>`, `RwLock<T>`, and `Condvar` that replace
9//! `parking_lot` throughout the Noxu codebase.
10//!
11//! ## Extra capabilities vs parking_lot
12//!
13//! | Method | Description |
14//! |--------|-------------|
15//! | `Mutex::get_n_waiters()` | Count of threads blocked waiting for the mutex |
16//! | `Mutex::get_owner()` | Thread ID hash of the current owner |
17//! | `RwLock::get_n_waiters()` | Count of threads blocked waiting for the rwlock |
18//! | `RwLock::is_locked_exclusive()` | Returns true when a write lock is held |
19//! | `RwLock::reader_count()` | Number of active shared-lock holders (global) |
20//!
21//! ## Drop-in compatibility
22//!
23//! The public types are designed to be drop-in replacements for the
24//! corresponding `parking_lot` types:
25//!
26//! ```text
27//! parking_lot::Mutex<T>          →  noxu_sync::Mutex<T>
28//! parking_lot::RwLock<T>         →  noxu_sync::RwLock<T>
29//! parking_lot::Condvar           →  noxu_sync::Condvar
30//! parking_lot::RawMutex          →  noxu_sync::RawMutex
31//! parking_lot::lock_api::…       →  noxu_sync::lock_api::…
32//! parking_lot::MutexGuard<'_,T>  →  noxu_sync::MutexGuard<'_,T>
33//! parking_lot::WaitTimeoutResult →  noxu_sync::WaitTimeoutResult
34//! ```
35
36pub mod condvar;
37pub mod futex;
38pub mod raw_mutex;
39pub mod raw_rwlock;
40
41pub use condvar::{Condvar, WaitTimeoutResult};
42pub use raw_mutex::NoxuRawMutex;
43pub use raw_rwlock::NoxuRawRwLock;
44
45/// Re-export `lock_api` so callers can do `use noxu_sync::lock_api::RawMutex`.
46pub use lock_api;
47
48// ---------------------------------------------------------------------------
49// Mutex
50// ---------------------------------------------------------------------------
51
52/// Mutual exclusion primitive backed by a futex.
53///
54/// Drop-in replacement for `parking_lot::Mutex<T>`.
55pub type Mutex<T> = lock_api::Mutex<NoxuRawMutex, T>;
56
57/// RAII guard returned by `Mutex::lock`.
58pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, NoxuRawMutex, T>;
59
60/// The raw mutex type for direct embed (e.g., `log_buffer.rs`).
61///
62/// Provides `RawMutex::INIT` for const initialisation and implements
63/// `lock_api::RawMutex` for `.lock()` / `unsafe .unlock()`.
64pub type RawMutex = NoxuRawMutex;
65
66// ---------------------------------------------------------------------------
67// RwLock
68// ---------------------------------------------------------------------------
69
70/// RAII guard returned by `RwLock::read`.
71pub type RwLockReadGuard<'a, T> =
72    lock_api::RwLockReadGuard<'a, NoxuRawRwLock, T>;
73
74/// RAII guard returned by `RwLock::write`.
75pub type RwLockWriteGuard<'a, T> =
76    lock_api::RwLockWriteGuard<'a, NoxuRawRwLock, T>;
77
78/// Reader-writer lock backed by a futex.
79///
80/// Drop-in replacement for `parking_lot::RwLock<T>`.
81/// Non-fair: new readers are not blocked by waiting writers.
82///
83/// Additional methods beyond parking_lot:
84///   - `is_locked_exclusive()` — true when a write lock is held
85///   - `get_n_waiters()`       — number of threads waiting (read + write)
86///   - `reader_count()`        — number of active readers
87pub struct RwLock<T>(lock_api::RwLock<NoxuRawRwLock, T>);
88
89impl<T> RwLock<T> {
90    /// Creates a new `RwLock` wrapping `val`.
91    #[inline]
92    pub fn new(val: T) -> Self {
93        RwLock(lock_api::RwLock::new(val))
94    }
95
96    /// Acquires a shared (read) lock, blocking until available.
97    #[inline]
98    pub fn read(&self) -> RwLockReadGuard<'_, T> {
99        self.0.read()
100    }
101
102    /// Acquires an exclusive (write) lock, blocking until available.
103    #[inline]
104    pub fn write(&self) -> RwLockWriteGuard<'_, T> {
105        self.0.write()
106    }
107
108    /// Tries to acquire a shared lock without blocking.
109    #[inline]
110    pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {
111        self.0.try_read()
112    }
113
114    /// Tries to acquire an exclusive lock without blocking.
115    #[inline]
116    pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>> {
117        self.0.try_write()
118    }
119
120    /// Tries to acquire a shared lock within the given `timeout`.
121    #[inline]
122    pub fn try_read_for(
123        &self,
124        timeout: std::time::Duration,
125    ) -> Option<RwLockReadGuard<'_, T>> {
126        self.0.try_read_for(timeout)
127    }
128
129    /// Tries to acquire an exclusive lock within the given `timeout`.
130    #[inline]
131    pub fn try_write_for(
132        &self,
133        timeout: std::time::Duration,
134    ) -> Option<RwLockWriteGuard<'_, T>> {
135        self.0.try_write_for(timeout)
136    }
137
138    /// Returns `true` if the lock is held by any reader or by the exclusive writer.
139    #[inline]
140    pub fn is_locked(&self) -> bool {
141        self.0.is_locked()
142    }
143
144    /// Returns `true` if the write (exclusive) lock is currently held.
145    #[inline]
146    pub fn is_locked_exclusive(&self) -> bool {
147        // SAFETY: raw() is safe to call; we only read atomic state.
148        unsafe { self.0.raw().is_write_locked() }
149    }
150
151    /// Returns the total number of threads waiting to acquire this lock.
152    #[inline]
153    pub fn get_n_waiters(&self) -> usize {
154        unsafe { self.0.raw().get_n_waiters() }
155    }
156
157    /// Returns the number of active readers.
158    #[inline]
159    pub fn reader_count(&self) -> u32 {
160        unsafe { self.0.raw().reader_count() }
161    }
162
163    /// Returns a reference to the raw `NoxuRawRwLock`.
164    #[inline]
165    pub fn raw(&self) -> &NoxuRawRwLock {
166        unsafe { self.0.raw() }
167    }
168}
169
170// ---------------------------------------------------------------------------
171// Tests
172// ---------------------------------------------------------------------------
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use std::sync::Arc;
178    use std::time::Duration;
179
180    // --- Mutex ---
181
182    #[test]
183    fn mutex_basic() {
184        let m = Mutex::new(0i32);
185        *m.lock() = 42;
186        assert_eq!(*m.lock(), 42);
187    }
188
189    #[test]
190    fn mutex_try_lock() {
191        let m = Arc::new(Mutex::new(()));
192        let g = m.lock();
193        let m2 = m.clone();
194        let failed =
195            std::thread::spawn(move || m2.try_lock().is_none()).join().unwrap();
196        assert!(failed);
197        drop(g);
198        assert!(m.try_lock().is_some());
199    }
200
201    #[test]
202    fn mutex_try_lock_for_timeout() {
203        let m = Arc::new(Mutex::new(()));
204        let g = m.lock();
205        let m2 = m.clone();
206        let timed_out = std::thread::spawn(move || {
207            m2.try_lock_for(Duration::from_millis(30)).is_none()
208        })
209        .join()
210        .unwrap();
211        assert!(timed_out);
212        drop(g);
213    }
214
215    #[test]
216    fn mutex_get_n_waiters() {
217        let m = Arc::new(Mutex::new(()));
218        let _g = m.lock();
219        let m2 = m.clone();
220        let barrier = Arc::new(std::sync::Barrier::new(2));
221        let b2 = barrier.clone();
222        let handle = std::thread::spawn(move || {
223            b2.wait();
224            let _g2 = m2.lock();
225        });
226        barrier.wait();
227        std::thread::sleep(Duration::from_millis(10));
228        // The raw() accessor exposes get_n_waiters.
229        assert!(unsafe { m.raw().get_n_waiters() } >= 1 || m.is_locked());
230        drop(_g);
231        handle.join().unwrap();
232    }
233
234    #[test]
235    fn mutex_force_unlock() {
236        let m = Mutex::new(());
237        let _g = m.lock();
238        unsafe { m.force_unlock() };
239        // Should be acquirable now.
240        assert!(m.try_lock().is_some());
241    }
242
243    // --- RwLock ---
244
245    #[test]
246    fn rwlock_basic_read_write() {
247        let rw = RwLock::new(0i32);
248        *rw.write() = 99;
249        assert_eq!(*rw.read(), 99);
250    }
251
252    #[test]
253    fn rwlock_multiple_readers() {
254        let rw = Arc::new(RwLock::new(42i32));
255        let rw2 = rw.clone();
256        let g1 = rw.read();
257        let handle = std::thread::spawn(move || {
258            let g2 = rw2.read();
259            *g2
260        });
261        assert_eq!(*g1, 42);
262        assert_eq!(handle.join().unwrap(), 42);
263    }
264
265    #[test]
266    fn rwlock_exclusive_blocks_readers() {
267        let rw = Arc::new(RwLock::new(()));
268        let _wg = rw.write();
269        let rw2 = rw.clone();
270        let failed = std::thread::spawn(move || rw2.try_read().is_none())
271            .join()
272            .unwrap();
273        assert!(failed);
274    }
275
276    #[test]
277    fn rwlock_is_locked_exclusive() {
278        let rw = RwLock::new(());
279        assert!(!rw.is_locked_exclusive());
280        let _wg = rw.write();
281        assert!(rw.is_locked_exclusive());
282    }
283
284    #[test]
285    fn rwlock_try_write_for_timeout() {
286        let rw = Arc::new(RwLock::new(()));
287        let _wg = rw.write();
288        let rw2 = rw.clone();
289        let timed_out = std::thread::spawn(move || {
290            rw2.try_write_for(Duration::from_millis(30)).is_none()
291        })
292        .join()
293        .unwrap();
294        assert!(timed_out);
295    }
296
297    #[test]
298    fn rwlock_try_read_for_timeout() {
299        let rw = Arc::new(RwLock::new(()));
300        let _wg = rw.write();
301        let rw2 = rw.clone();
302        let timed_out = std::thread::spawn(move || {
303            rw2.try_read_for(Duration::from_millis(30)).is_none()
304        })
305        .join()
306        .unwrap();
307        assert!(timed_out);
308    }
309
310    // --- RawMutex (for log_buffer style usage) ---
311
312    #[test]
313    fn raw_mutex_const_init_and_lock_unlock() {
314        use lock_api::RawMutex as RawMutexTrait;
315        let raw = NoxuRawMutex::INIT;
316        assert!(!raw.is_locked());
317        raw.lock();
318        assert!(raw.is_locked());
319        unsafe { raw.unlock() };
320        assert!(!raw.is_locked());
321    }
322}