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