Skip to main content

noxu_latch/
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#![allow(dead_code, clippy::type_complexity, clippy::too_many_arguments)]
7//! > **Internal component of the [`noxu`](https://crates.io/crates/noxu) database.**
8//! >
9//! > This crate is published only so the `noxu` umbrella crate can depend on it.
10//! > Use `noxu` (`noxu = "3"`) in applications; depend on this crate directly only
11//! > if you are extending the engine internals. Its API may change without a major
12//! > version bump.
13//!
14//! Latching primitives for Noxu DB.
15//!
16//! Latching primitives — exclusive and shared/exclusive latches used for
17//! B-tree node concurrency control.
18//!
19//! Latches are expected to be held for short, defined periods of time. No
20//! deadlock detection is provided; it is the caller's responsibility to
21//! sequence latch acquisition in an ordered fashion to avoid deadlocks.
22//!
23//! Key properties:
24//! - Uses `noxu_sync` for the underlying lock primitives
25//! - Reentrancy prevention is enforced (panics on reentrant acquire)
26//! - Thread ownership tracking is always available via noxu_sync
27
28mod exclusive;
29mod shared;
30
31pub use exclusive::{ExclusiveLatch, ExclusiveLatchGuard};
32pub use shared::{SharedLatch, SharedLatchReadGuard, SharedLatchWriteGuard};
33
34use std::fmt;
35use std::time::Duration;
36
37/// Default latch timeout.
38pub const DEFAULT_LATCH_TIMEOUT: Duration = Duration::from_secs(5);
39
40/// Context information about a latch, used for debugging and diagnostics.
41///
42/// Stores the latch name and acquisition timeout directly.
43/// In B-tree nodes the name identifies which node the latch protects.
44#[derive(Debug, Clone)]
45pub struct LatchContext {
46    /// Name of this latch for debugging.
47    pub name: String,
48    /// Timeout for acquiring this latch.
49    pub timeout: Duration,
50}
51
52impl LatchContext {
53    /// Creates a new latch context with the given name and default timeout.
54    pub fn new(name: impl Into<String>) -> Self {
55        LatchContext { name: name.into(), timeout: DEFAULT_LATCH_TIMEOUT }
56    }
57
58    /// Creates a new latch context with the given name and timeout.
59    pub fn with_timeout(name: impl Into<String>, timeout: Duration) -> Self {
60        LatchContext { name: name.into(), timeout }
61    }
62}
63
64impl fmt::Display for LatchContext {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "{}", self.name)
67    }
68}
69
70/// Errors that can occur during latch operations.
71#[derive(Debug)]
72pub enum LatchError {
73    /// The latch is already held by the current thread (reentrancy detected).
74    AlreadyHeld(String),
75    /// The latch is not held by the current thread on release.
76    NotHeld(String),
77    /// The latch acquisition timed out.
78    Timeout(String),
79}
80
81impl fmt::Display for LatchError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            LatchError::AlreadyHeld(msg) => {
85                write!(f, "Latch already held: {}", msg)
86            }
87            LatchError::NotHeld(msg) => write!(f, "Latch not held: {}", msg),
88            LatchError::Timeout(msg) => write!(f, "Latch timeout: {}", msg),
89        }
90    }
91}
92
93impl std::error::Error for LatchError {}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_latch_context_default_timeout() {
101        let ctx = LatchContext::new("my-latch");
102        assert_eq!(ctx.name, "my-latch");
103        assert_eq!(ctx.timeout, DEFAULT_LATCH_TIMEOUT);
104    }
105
106    #[test]
107    fn test_latch_context_with_timeout() {
108        use std::time::Duration;
109        let ctx =
110            LatchContext::with_timeout("custom", Duration::from_millis(100));
111        assert_eq!(ctx.name, "custom");
112        assert_eq!(ctx.timeout, Duration::from_millis(100));
113    }
114
115    #[test]
116    fn test_latch_context_display() {
117        let ctx = LatchContext::new("test-display");
118        assert_eq!(format!("{}", ctx), "test-display");
119    }
120
121    #[test]
122    fn test_latch_error_display() {
123        let e1 = LatchError::AlreadyHeld("foo".to_string());
124        assert!(
125            format!("{}", e1).contains("already held")
126                || format!("{}", e1).contains("Latch already held")
127        );
128
129        let e2 = LatchError::NotHeld("bar".to_string());
130        assert!(
131            format!("{}", e2).contains("not held")
132                || format!("{}", e2).contains("Latch not held")
133        );
134
135        let e3 = LatchError::Timeout("baz".to_string());
136        assert!(
137            format!("{}", e3).contains("timeout")
138                || format!("{}", e3).contains("Latch timeout")
139        );
140    }
141
142    #[test]
143    fn test_latch_error_is_error() {
144        use std::error::Error;
145        let e = LatchError::Timeout("x".to_string());
146        let _: &dyn Error = &e;
147    }
148}