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}