Skip to main content

lock_db/
error.rs

1//! Error types returned by the lock manager.
2
3use core::fmt;
4
5/// Reasons a lock operation can fail.
6///
7/// Every fallible entry point on [`LockManager`](crate::LockManager) returns
8/// `Result<_, LockError>`. The variants are deliberately coarse: a caller
9/// either got the lock or it did not, and the few ways "did not" can happen are
10/// distinct enough to branch on. The type is `#[non_exhaustive]` because later
11/// milestones (wait queues, deadlock detection) add variants such as a timeout
12/// and a deadlock-victim signal; matching code must keep a wildcard arm.
13///
14/// # Examples
15///
16/// ```
17/// use lock_db::{LockError, LockManager, LockMode, ResourceId, TxnId};
18///
19/// let lm = LockManager::new();
20/// let row = ResourceId::new(1);
21///
22/// // Txn 1 takes an exclusive lock.
23/// lm.try_acquire(TxnId::new(1), row, LockMode::Exclusive).unwrap();
24///
25/// // Txn 2 cannot get any lock on the same row right now.
26/// assert_eq!(lm.try_acquire(TxnId::new(2), row, LockMode::Shared), Err(LockError::Conflict));
27///
28/// // Releasing a lock you never held is a distinct error.
29/// assert_eq!(lm.release(TxnId::new(9), row), Err(LockError::NotHeld));
30/// ```
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[non_exhaustive]
34pub enum LockError {
35    /// The lock could not be granted without blocking.
36    ///
37    /// The requested mode is incompatible with a mode another transaction
38    /// already holds on the resource, or the request is an upgrade
39    /// (shared to exclusive) that other shared holders are blocking. Returned
40    /// by the non-blocking [`try_acquire`](crate::LockManager::try_acquire);
41    /// the caller decides whether to retry, wait, or abort.
42    Conflict,
43
44    /// A release named a (transaction, resource) pair that holds no lock.
45    ///
46    /// Returned by [`release`](crate::LockManager::release) when the
47    /// transaction does not currently hold a lock on the resource. This usually
48    /// signals a double release or a bookkeeping bug in the caller's
49    /// transaction layer.
50    NotHeld,
51}
52
53impl fmt::Display for LockError {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::Conflict => f.write_str("lock request conflicts with an existing lock"),
57            Self::NotHeld => f.write_str("no lock held for this transaction and resource"),
58        }
59    }
60}
61
62#[cfg(feature = "std")]
63impl std::error::Error for LockError {}
64
65#[cfg(test)]
66mod tests {
67    use super::LockError;
68
69    // `to_string` needs an allocator, so this one is only built with `std`.
70    #[cfg(feature = "std")]
71    #[test]
72    fn test_display_messages_are_distinct_and_nonempty() {
73        let conflict = LockError::Conflict.to_string();
74        let not_held = LockError::NotHeld.to_string();
75        assert!(!conflict.is_empty());
76        assert!(!not_held.is_empty());
77        assert_ne!(conflict, not_held);
78    }
79
80    #[test]
81    fn test_variants_compare_equal_to_themselves() {
82        assert_eq!(LockError::Conflict, LockError::Conflict);
83        assert_ne!(LockError::Conflict, LockError::NotHeld);
84    }
85}