Skip to main content

noxu_txn/
locker.rs

1//! Base Locker trait.
2//!
3
4use crate::{LockResult, LockType, TxnError};
5
6/// Null transaction ID — used by non-transactional lockers (BasicLocker, etc.).
7///
8///
9pub const NULL_TXN_ID: i64 = -1;
10
11/// A Locker is route to locking and transactional support.
12///
13/// This trait is the abstract base for BasicLocker, ThreadLocker, HandleLocker,
14/// and Txn. Locker instances are a transaction shell to get to the lock manager,
15/// and don't guarantee transactional semantics by themselves.
16///
17/// Only Txn (and its subclasses like MasterTxn, ReadonlyTxn) instances are
18/// truly transactional with commit/abort semantics.
19///
20///
21pub trait Locker: Send + Sync {
22    /// Returns the unique ID of this locker.
23    ///
24    /// For BasicLocker and ThreadLocker, this may be a shared constant.
25    /// For Txn, this is a unique transaction ID used for recovery.
26    fn id(&self) -> i64;
27
28    /// Acquires a lock on the given LSN.
29    ///
30    /// This is the main locking entry point. Implementations determine
31    /// how to interact with the LockManager and what to do with write locks.
32    ///
33    /// # Arguments
34    /// * `lsn` - LSN of the record to lock
35    /// * `lock_type` - Type of lock to acquire
36    /// * `non_blocking` - If true, don't wait for lock (fail immediately if unavailable)
37    fn lock(
38        &mut self,
39        lsn: u64,
40        lock_type: LockType,
41        non_blocking: bool,
42    ) -> Result<LockResult, TxnError>;
43
44    /// Releases a lock on the given LSN.
45    ///
46    /// For non-transactional lockers, this releases the lock immediately.
47    /// For transactional lockers, this may defer release until commit/abort.
48    fn release_lock(&mut self, lsn: u64) -> Result<(), TxnError>;
49
50    /// Returns true if this locker owns a write lock on the given LSN.
51    fn owns_write_lock(&self, lsn: u64) -> bool;
52
53    /// Returns true if this locker owns ANY lock (read or write) on the given LSN.
54    ///
55    /// Used to guard against an illegal RangeRead → RangeInsert upgrade when
56    /// the same SERIALIZABLE transaction both scans and inserts into the same
57    /// key range.  The default implementation falls back to `owns_write_lock`;
58    /// `Txn` overrides this to also check `read_locks`.
59    fn owns_any_lock(&self, lsn: u64) -> bool {
60        self.owns_write_lock(lsn)
61    }
62
63    /// Returns true if this locker is transactional (supports commit/abort).
64    ///
65    /// BasicLocker, ThreadLocker, and HandleLocker return false.
66    /// Txn and its subclasses return true.
67    fn is_transactional(&self) -> bool;
68
69    /// Returns true if locks should be retained on commit (serializable isolation).
70    ///
71    /// Default is false. Txn with SERIALIZABLE isolation overrides this.
72    fn retains_locks_on_commit(&self) -> bool {
73        false
74    }
75
76    /// Returns the timeout for lock attempts in milliseconds.
77    ///
78    /// Zero means infinite timeout (wait forever).
79    fn lock_timeout_ms(&self) -> u64;
80
81    /// Returns true if this locker uses non-blocking lock requests by default.
82    ///
83    /// Default is false. Some specialized lockers may override this.
84    fn default_no_wait(&self) -> bool {
85        false
86    }
87
88    /// Returns true if this locker's locks can be preempted/stolen.
89    ///
90    /// Default is true. Replayer lockers in HA may steal locks from
91    /// application lockers to maintain replica consistency.
92    fn is_preemptable(&self) -> bool {
93        true
94    }
95
96    /// Returns true if this locker can steal other lockers' locks.
97    ///
98    /// Default is false. Replayer lockers return true.
99    fn is_importunate(&self) -> bool {
100        false
101    }
102
103    /// Returns true if this locker allows read-uncommitted by default.
104    ///
105    /// Default is false. Can be set via isolation level configuration.
106    fn is_read_uncommitted_default(&self) -> bool {
107        false
108    }
109
110    /// Returns true if this locker shares locks with the locker identified by
111    /// `other_id`.
112    ///
113    /// ThreadLockers on the same thread
114    /// return true, allowing multiple cursors on the same thread to operate
115    /// without lock conflicts.  HandleLocker returns true when configured with
116    /// a buddy locker.  Default: false.
117    ///
118    /// Used by `LockImpl::try_lock()` to skip conflict detection between
119    /// lockers that are known to cooperate.
120    fn shares_locks_with(&self, other_id: i64) -> bool {
121        let _ = other_id;
122        false
123    }
124
125    /// Returns true if locking is required for this locker's current context.
126    ///
127    /// Set to `!cursor.isInternalDbCursor()`
128    /// by `registerCursor()`.  When false, `DummyLockManager` grants locks
129    /// without consulting the underlying lock table.
130    ///
131    /// Default: true.  Override in BasicLocker (and its subclasses) to respect
132    /// the internal-DB-cursor optimization.
133    fn locking_required(&self) -> bool {
134        true
135    }
136
137    /// Returns the transaction-level timeout in milliseconds.
138    ///
139    /// A value of 0 means no transaction timeout (only lock timeout applies).
140    ///
141    /// `Locker.txnTimeoutMillis`.  Default: 0.
142    fn txn_timeout_ms(&self) -> u64 {
143        0
144    }
145
146    /// Returns true if the transaction-level timeout has expired.
147    ///
148    /// `Locker.isTimedOut()`.  Default: false (no timeout set).
149    fn is_timed_out(&self) -> bool {
150        false
151    }
152
153    /// Called by the lock manager when an LN is moved to a new LSN without
154    /// first acquiring a write lock (e.g. during eviction or cleaning).
155    ///
156    /// Every locker holding `old_lsn` must acquire a lock on `new_lsn` so that
157    /// the undo chain remains intact.
158    ///
159    /// Default: no-op.
160    fn lock_after_lsn_change(
161        &mut self,
162        _old_lsn: u64,
163        _new_lsn: u64,
164    ) -> Result<(), TxnError> {
165        Ok(())
166    }
167
168    /// Called at the end of a non-transactional operation to release locks.
169    ///
170    /// For BasicLocker this releases all locks
171    /// and closes the locker; for Txn this is a no-op.
172    /// Default: no-op.
173    fn operation_end(&mut self) -> Result<(), TxnError> {
174        Ok(())
175    }
176
177    /// Releases all non-transactional locks held by this locker.
178    ///
179    /// Called during non-txn operation
180    /// cleanup to release any read locks acquired during a cursor scan.
181    /// Default: no-op.
182    fn release_non_txn_locks(&mut self) -> Result<(), TxnError> {
183        Ok(())
184    }
185
186    /// Called after a non-transactional operation ends, releasing locks and
187    /// closing the locker.
188    ///
189    /// Differs from `operationEnd()` in
190    /// that it also closes the locker.
191    /// Default: delegates to `operation_end()`.
192    fn non_txn_operation_end(&mut self) -> Result<(), TxnError> {
193        self.operation_end()
194    }
195
196    /// Returns true if this locker uses serializable (repeatable-read) isolation.
197    ///
198    /// `Locker.isSerializableIsolation()`.  Default: false.
199    fn is_serializable_isolation(&self) -> bool {
200        false
201    }
202
203    /// Returns true if this locker uses read-committed isolation.
204    ///
205    /// `Locker.isReadCommittedIsolation()`.  Default: false.
206    fn is_read_committed_isolation(&self) -> bool {
207        false
208    }
209
210    /// Returns the transaction ID if this locker is or owns a Txn, else None.
211    ///
212    /// Returns `this` for Txn, null for others.
213    /// Default: None.
214    fn get_txn_locker_id(&self) -> Option<i64> {
215        None
216    }
217
218    /// Marks this locker as closed. After close, no operations should occur.
219    ///
220    /// Implementations should release any held locks and clean up resources.
221    fn close(&mut self);
222
223    /// Returns true if this locker is still open.
224    fn is_open(&self) -> bool;
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::LockGrantType;
231
232    /// Test that trait methods have correct defaults.
233    struct TestLocker {
234        id: i64,
235        is_open: bool,
236    }
237
238    impl Locker for TestLocker {
239        fn id(&self) -> i64 {
240            self.id
241        }
242
243        fn lock(
244            &mut self,
245            _lsn: u64,
246            _lock_type: LockType,
247            _non_blocking: bool,
248        ) -> Result<LockResult, TxnError> {
249            Ok(LockResult::new(LockGrantType::New, None))
250        }
251
252        fn release_lock(&mut self, _lsn: u64) -> Result<(), TxnError> {
253            Ok(())
254        }
255
256        fn owns_write_lock(&self, _lsn: u64) -> bool {
257            false
258        }
259
260        fn is_transactional(&self) -> bool {
261            false
262        }
263
264        fn lock_timeout_ms(&self) -> u64 {
265            5000
266        }
267
268        fn close(&mut self) {
269            self.is_open = false;
270        }
271
272        fn is_open(&self) -> bool {
273            self.is_open
274        }
275    }
276
277    #[test]
278    fn test_defaults() {
279        let locker = TestLocker { id: 1, is_open: true };
280        assert!(!locker.retains_locks_on_commit());
281        assert!(!locker.default_no_wait());
282        assert!(locker.is_preemptable());
283        assert!(!locker.is_importunate());
284        assert!(!locker.is_read_uncommitted_default());
285    }
286
287    #[test]
288    fn test_close() {
289        let mut locker = TestLocker { id: 1, is_open: true };
290        assert!(locker.is_open());
291        locker.close();
292        assert!(!locker.is_open());
293    }
294
295    // -----------------------------------------------------------------------
296    // Additional coverage for default trait methods and direct trait-object coercion
297    // (Rust 1.86 makes &dyn SubTrait → &dyn SuperTrait coercion implicit)
298    // -----------------------------------------------------------------------
299
300    #[test]
301    fn test_id() {
302        let locker = TestLocker { id: 42, is_open: true };
303        assert_eq!(locker.id(), 42);
304    }
305
306    #[test]
307    fn test_is_not_transactional() {
308        let locker = TestLocker { id: 1, is_open: true };
309        assert!(!locker.is_transactional());
310    }
311
312    #[test]
313    fn test_lock_timeout_ms() {
314        let locker = TestLocker { id: 1, is_open: true };
315        assert_eq!(locker.lock_timeout_ms(), 5000);
316    }
317
318    #[test]
319    fn test_release_lock_ok() {
320        let mut locker = TestLocker { id: 1, is_open: true };
321        // TestLocker::release_lock is a no-op returning Ok
322        assert!(locker.release_lock(100).is_ok());
323    }
324
325    #[test]
326    fn test_owns_write_lock_always_false() {
327        let locker = TestLocker { id: 1, is_open: true };
328        assert!(!locker.owns_write_lock(100));
329        assert!(!locker.owns_write_lock(0));
330    }
331
332    #[test]
333    fn test_retains_locks_on_commit_default() {
334        let locker = TestLocker { id: 1, is_open: true };
335        // Default implementation returns false
336        assert!(!locker.retains_locks_on_commit());
337    }
338
339    #[test]
340    fn test_default_no_wait_default() {
341        let locker = TestLocker { id: 1, is_open: true };
342        // Default implementation returns false
343        assert!(!locker.default_no_wait());
344    }
345
346    #[test]
347    fn test_is_preemptable_default() {
348        let locker = TestLocker { id: 1, is_open: true };
349        // Default implementation returns true
350        assert!(locker.is_preemptable());
351    }
352
353    #[test]
354    fn test_is_importunate_default() {
355        let locker = TestLocker { id: 1, is_open: true };
356        // Default implementation returns false
357        assert!(!locker.is_importunate());
358    }
359
360    #[test]
361    fn test_is_read_uncommitted_default() {
362        let locker = TestLocker { id: 1, is_open: true };
363        // Default implementation returns false
364        assert!(!locker.is_read_uncommitted_default());
365    }
366
367    #[test]
368    fn test_locker_as_dyn_ref() {
369        let locker = TestLocker { id: 7, is_open: true };
370        // Direct coercion to &dyn Locker (Rust 1.86 — no helper trait needed).
371        let as_ref: &dyn Locker = &locker;
372        assert_eq!(as_ref.id(), 7);
373        assert!(as_ref.is_open());
374    }
375
376    #[test]
377    fn test_locker_as_dyn_mut() {
378        let mut locker = TestLocker { id: 7, is_open: true };
379        {
380            let as_mut: &mut dyn Locker = &mut locker;
381            as_mut.close();
382        }
383        assert!(!locker.is_open());
384    }
385
386    #[test]
387    fn test_multiple_closes_idempotent() {
388        let mut locker = TestLocker { id: 1, is_open: true };
389        locker.close();
390        assert!(!locker.is_open());
391        // Closing an already-closed locker should not panic
392        locker.close();
393        assert!(!locker.is_open());
394    }
395
396    /// A locker that overrides all default methods to non-default values,
397    /// to verify those code paths are exercised.
398    struct CustomDefaultsLocker;
399
400    impl Locker for CustomDefaultsLocker {
401        fn id(&self) -> i64 {
402            99
403        }
404
405        fn lock(
406            &mut self,
407            _lsn: u64,
408            _lock_type: LockType,
409            _non_blocking: bool,
410        ) -> Result<LockResult, TxnError> {
411            Ok(LockResult::new(LockGrantType::New, None))
412        }
413
414        fn release_lock(&mut self, _lsn: u64) -> Result<(), TxnError> {
415            Ok(())
416        }
417
418        fn owns_write_lock(&self, _lsn: u64) -> bool {
419            false
420        }
421
422        fn is_transactional(&self) -> bool {
423            true
424        }
425
426        fn lock_timeout_ms(&self) -> u64 {
427            0
428        }
429
430        fn close(&mut self) {}
431
432        fn is_open(&self) -> bool {
433            true
434        }
435
436        // Override all the default methods to non-default values
437        fn retains_locks_on_commit(&self) -> bool {
438            true
439        }
440        fn default_no_wait(&self) -> bool {
441            true
442        }
443        fn is_preemptable(&self) -> bool {
444            false
445        }
446        fn is_importunate(&self) -> bool {
447            true
448        }
449        fn is_read_uncommitted_default(&self) -> bool {
450            true
451        }
452    }
453
454    #[test]
455    fn test_custom_defaults_overrides() {
456        let locker = CustomDefaultsLocker;
457        assert!(locker.retains_locks_on_commit());
458        assert!(locker.default_no_wait());
459        assert!(!locker.is_preemptable());
460        assert!(locker.is_importunate());
461        assert!(locker.is_read_uncommitted_default());
462        assert!(locker.is_transactional());
463        assert_eq!(locker.lock_timeout_ms(), 0);
464    }
465
466    #[test]
467    fn test_custom_locker_as_dyn_ref() {
468        let locker = CustomDefaultsLocker;
469        let as_ref: &dyn Locker = &locker;
470        assert_eq!(as_ref.id(), 99);
471        assert!(as_ref.retains_locks_on_commit());
472    }
473}