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