Skip to main content

reddb_server/runtime/
lock_manager.rs

1//! Intent lock management for runtime dispatch.
2//!
3//! This is the live runtime lock manager used by dispatch-time
4//! collection and DDL locking. It is deliberately separate from the
5//! retired transaction coordinator modules described by ADR 0065.
6
7use std::collections::{HashMap, HashSet, VecDeque};
8use std::sync::{
9    Condvar, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, WaitTimeoutResult,
10};
11use std::time::{Duration, Instant};
12
13/// Transaction ID type
14pub type TxnId = u64;
15
16fn read_unpoisoned<'a, T>(lock: &'a RwLock<T>) -> RwLockReadGuard<'a, T> {
17    match lock.read() {
18        Ok(guard) => guard,
19        Err(poisoned) => poisoned.into_inner(),
20    }
21}
22
23fn write_unpoisoned<'a, T>(lock: &'a RwLock<T>) -> RwLockWriteGuard<'a, T> {
24    match lock.write() {
25        Ok(guard) => guard,
26        Err(poisoned) => poisoned.into_inner(),
27    }
28}
29
30fn mutex_unpoisoned<'a, T>(lock: &'a Mutex<T>) -> MutexGuard<'a, T> {
31    match lock.lock() {
32        Ok(guard) => guard,
33        Err(poisoned) => poisoned.into_inner(),
34    }
35}
36
37fn wait_timeout_unpoisoned<'a, T>(
38    condvar: &Condvar,
39    guard: MutexGuard<'a, T>,
40    timeout: Duration,
41) -> (MutexGuard<'a, T>, WaitTimeoutResult) {
42    match condvar.wait_timeout(guard, timeout) {
43        Ok(result) => result,
44        Err(poisoned) => poisoned.into_inner(),
45    }
46}
47
48/// Lock modes
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum LockMode {
51    /// Shared lock (read)
52    Shared,
53    /// Exclusive lock (write)
54    Exclusive,
55    /// Intent shared (for hierarchical locking)
56    IntentShared,
57    /// Intent exclusive (for hierarchical locking)
58    IntentExclusive,
59    /// Shared + Intent exclusive
60    SharedIntentExclusive,
61}
62
63impl LockMode {
64    /// Check if this mode is compatible with another
65    pub fn is_compatible(&self, other: &LockMode) -> bool {
66        use LockMode::*;
67        matches!(
68            (self, other),
69            (Shared, Shared)
70                | (Shared, IntentShared)
71                | (IntentShared, Shared)
72                | (IntentShared, IntentShared)
73                | (IntentShared, IntentExclusive)
74                | (IntentExclusive, IntentShared)
75                | (IntentExclusive, IntentExclusive)
76        )
77    }
78
79    /// Check if this mode can be upgraded to another
80    pub fn can_upgrade_to(&self, target: &LockMode) -> bool {
81        use LockMode::*;
82        matches!(
83            (self, target),
84            (Shared, Exclusive)
85                | (IntentShared, IntentExclusive)
86                | (IntentShared, SharedIntentExclusive)
87                | (Shared, SharedIntentExclusive)
88        )
89    }
90}
91
92/// Lock request result
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum LockResult {
95    /// Lock granted immediately
96    Granted,
97    /// Lock request is waiting
98    Waiting,
99    /// Deadlock detected, request denied (contains cycle of transaction IDs)
100    Deadlock(Vec<TxnId>),
101    /// Timeout waiting for lock
102    Timeout,
103    /// Lock upgrade granted
104    Upgraded,
105    /// Lock already held
106    AlreadyHeld,
107    /// Transaction not found
108    TxnNotFound,
109    /// Lock limit exceeded for transaction
110    LockLimitExceeded,
111}
112
113/// Lock waiter information
114#[derive(Debug, Clone)]
115pub struct LockWaiter {
116    /// Transaction ID
117    pub txn_id: TxnId,
118    /// Requested lock mode
119    pub mode: LockMode,
120    /// When the wait started
121    pub start_time: Instant,
122    /// Maximum wait time
123    pub timeout: Duration,
124}
125
126impl LockWaiter {
127    /// Create new waiter
128    pub fn new(txn_id: TxnId, mode: LockMode, timeout: Duration) -> Self {
129        Self {
130            txn_id,
131            mode,
132            start_time: Instant::now(),
133            timeout,
134        }
135    }
136
137    /// Check if wait has timed out
138    pub fn is_timed_out(&self) -> bool {
139        self.start_time.elapsed() > self.timeout
140    }
141}
142
143/// A single lock on a resource
144#[derive(Debug)]
145struct Lock {
146    /// Resource being locked
147    resource: Vec<u8>,
148    /// Current lock holders (txn_id -> mode)
149    holders: HashMap<TxnId, LockMode>,
150    /// Waiting requests
151    waiters: VecDeque<LockWaiter>,
152}
153
154impl Lock {
155    fn new(resource: Vec<u8>) -> Self {
156        Self {
157            resource,
158            holders: HashMap::new(),
159            waiters: VecDeque::new(),
160        }
161    }
162
163    /// Check if a mode can be granted given current holders
164    fn can_grant(&self, txn_id: TxnId, mode: LockMode) -> bool {
165        // If we already hold it, check upgrade
166        if let Some(held_mode) = self.holders.get(&txn_id) {
167            if *held_mode == mode {
168                return true; // Already have it
169            }
170            // Check if upgrade is possible
171            if held_mode.can_upgrade_to(&mode) {
172                // Can only upgrade if we're the only holder or others are compatible
173                return self
174                    .holders
175                    .iter()
176                    .all(|(id, m)| *id == txn_id || mode.is_compatible(m));
177            }
178            return false;
179        }
180
181        // Check compatibility with all holders
182        self.holders.values().all(|m| mode.is_compatible(m))
183    }
184
185    /// Grant lock to transaction
186    fn grant(&mut self, txn_id: TxnId, mode: LockMode) {
187        self.holders.insert(txn_id, mode);
188    }
189
190    /// Release lock from transaction
191    fn release(&mut self, txn_id: TxnId) -> Option<LockMode> {
192        self.holders.remove(&txn_id)
193    }
194
195    /// Add waiter
196    fn add_waiter(&mut self, waiter: LockWaiter) {
197        self.waiters.push_back(waiter);
198    }
199
200    /// Process waiters after a release
201    fn process_waiters(&mut self) -> Vec<TxnId> {
202        let mut granted = Vec::new();
203
204        // Remove timed out waiters
205        self.waiters.retain(|w| !w.is_timed_out());
206
207        // Try to grant waiting requests
208        let mut i = 0;
209        while i < self.waiters.len() {
210            let waiter = &self.waiters[i];
211            if self.can_grant(waiter.txn_id, waiter.mode) {
212                if let Some(waiter) = self.waiters.remove(i) {
213                    self.grant(waiter.txn_id, waiter.mode);
214                    granted.push(waiter.txn_id);
215                }
216            } else {
217                i += 1;
218            }
219        }
220
221        granted
222    }
223}
224
225/// Lock manager configuration
226#[derive(Debug, Clone)]
227pub struct LockConfig {
228    /// Default lock timeout
229    pub default_timeout: Duration,
230    /// Enable deadlock detection
231    pub deadlock_detection: bool,
232    /// Deadlock detection interval
233    pub detection_interval: Duration,
234    /// Maximum locks per transaction
235    pub max_locks_per_txn: usize,
236}
237
238impl Default for LockConfig {
239    fn default() -> Self {
240        Self {
241            default_timeout: Duration::from_secs(30),
242            deadlock_detection: true,
243            detection_interval: Duration::from_millis(100),
244            max_locks_per_txn: 10000,
245        }
246    }
247}
248
249/// Lock manager statistics
250#[derive(Debug, Clone, Default)]
251pub struct LockStats {
252    /// Total lock requests
253    pub requests: u64,
254    /// Immediately granted
255    pub granted: u64,
256    /// Had to wait
257    pub waited: u64,
258    /// Deadlocks detected
259    pub deadlocks: u64,
260    /// Timeouts
261    pub timeouts: u64,
262    /// Current active locks
263    pub active_locks: u64,
264    /// Current waiting requests
265    pub waiting: u64,
266}
267
268/// Lock manager for coordinating transaction locks
269pub struct LockManager {
270    /// Configuration
271    config: LockConfig,
272    /// Lock table: resource -> Lock
273    locks: RwLock<HashMap<Vec<u8>, Lock>>,
274    /// Transaction locks: txn_id -> resources held
275    txn_locks: RwLock<HashMap<TxnId, HashSet<Vec<u8>>>>,
276    /// Wait-for graph: txn_id -> txns it's waiting for
277    wait_graph: RwLock<HashMap<TxnId, HashSet<TxnId>>>,
278    /// Condition variable for waiters
279    waiter_cv: Condvar,
280    /// Mutex for condition variable
281    waiter_mutex: Mutex<()>,
282    /// Statistics
283    stats: RwLock<LockStats>,
284}
285
286impl LockManager {
287    /// Create new lock manager
288    pub fn new(config: LockConfig) -> Self {
289        Self {
290            config,
291            locks: RwLock::new(HashMap::new()),
292            txn_locks: RwLock::new(HashMap::new()),
293            wait_graph: RwLock::new(HashMap::new()),
294            waiter_cv: Condvar::new(),
295            waiter_mutex: Mutex::new(()),
296            stats: RwLock::new(LockStats::default()),
297        }
298    }
299
300    /// Create with default config
301    pub fn with_defaults() -> Self {
302        Self::new(LockConfig::default())
303    }
304
305    /// Acquire a lock
306    pub fn acquire(&self, txn_id: TxnId, resource: &[u8], mode: LockMode) -> LockResult {
307        self.acquire_with_timeout(txn_id, resource, mode, self.config.default_timeout)
308    }
309
310    /// Acquire lock with custom timeout
311    pub fn acquire_with_timeout(
312        &self,
313        txn_id: TxnId,
314        resource: &[u8],
315        mode: LockMode,
316        timeout: Duration,
317    ) -> LockResult {
318        // Update stats
319        {
320            let mut stats = write_unpoisoned(&self.stats);
321            stats.requests += 1;
322        }
323
324        let resource_key = resource.to_vec();
325
326        // Check lock limit
327        {
328            let txn_locks = read_unpoisoned(&self.txn_locks);
329            if let Some(locks) = txn_locks.get(&txn_id) {
330                if locks.len() >= self.config.max_locks_per_txn && !locks.contains(&resource_key) {
331                    return LockResult::LockLimitExceeded;
332                }
333            }
334        }
335
336        // Try to acquire immediately
337        {
338            let mut locks = write_unpoisoned(&self.locks);
339            let lock = locks
340                .entry(resource_key.clone())
341                .or_insert_with(|| Lock::new(resource_key.clone()));
342
343            if lock.can_grant(txn_id, mode) {
344                let already_held = lock.holders.contains_key(&txn_id);
345                lock.grant(txn_id, mode);
346
347                // Track in txn_locks
348                let mut txn_locks = write_unpoisoned(&self.txn_locks);
349                txn_locks.entry(txn_id).or_default().insert(resource_key);
350
351                let mut stats = write_unpoisoned(&self.stats);
352                stats.granted += 1;
353                stats.active_locks = locks.values().map(|l| l.holders.len() as u64).sum();
354
355                return if already_held {
356                    LockResult::Upgraded
357                } else {
358                    LockResult::Granted
359                };
360            }
361
362            // Can't grant immediately, need to wait
363            let waiting_for: HashSet<TxnId> = lock.holders.keys().copied().collect();
364
365            if self.config.deadlock_detection {
366                let mut wait_graph = Self::build_wait_graph_from_locks(&locks);
367                wait_graph
368                    .entry(txn_id)
369                    .or_default()
370                    .extend(waiting_for.iter().copied());
371
372                if self.detect_deadlock_inner(txn_id, &wait_graph) {
373                    let cycle: Vec<TxnId> = waiting_for.iter().copied().collect();
374                    let mut stats = write_unpoisoned(&self.stats);
375                    stats.deadlocks += 1;
376                    return LockResult::Deadlock(cycle);
377                }
378
379                *write_unpoisoned(&self.wait_graph) = wait_graph;
380            }
381
382            // Add to wait queue
383            if let Some(lock) = locks.get_mut(&resource_key) {
384                lock.add_waiter(LockWaiter::new(txn_id, mode, timeout));
385            }
386
387            let mut stats = write_unpoisoned(&self.stats);
388            stats.waited += 1;
389            stats.waiting += 1;
390        }
391
392        // Wait for lock
393        let start = Instant::now();
394        loop {
395            // Wait on condition variable
396            let guard = mutex_unpoisoned(&self.waiter_mutex);
397            let (_guard, _wait_result) =
398                wait_timeout_unpoisoned(&self.waiter_cv, guard, Duration::from_millis(10));
399
400            // Check if we got the lock
401            let holders: Option<HashSet<TxnId>> = {
402                let locks = read_unpoisoned(&self.locks);
403                if let Some(lock) = locks.get(&resource_key) {
404                    if lock.holders.contains_key(&txn_id) {
405                        // Remove from wait graph
406                        if self.config.deadlock_detection {
407                            let mut wait_graph = write_unpoisoned(&self.wait_graph);
408                            wait_graph.remove(&txn_id);
409                        }
410
411                        let mut stats = write_unpoisoned(&self.stats);
412                        stats.waiting -= 1;
413
414                        return LockResult::Granted;
415                    }
416
417                    Some(lock.holders.keys().copied().collect())
418                } else {
419                    None
420                }
421            };
422
423            if self.config.deadlock_detection {
424                let locks = read_unpoisoned(&self.locks);
425                let wait_graph = Self::build_wait_graph_from_locks(&locks);
426                drop(locks);
427
428                if self.detect_deadlock_inner(txn_id, &wait_graph) {
429                    let mut stats = write_unpoisoned(&self.stats);
430                    stats.deadlocks += 1;
431                    stats.waiting -= 1;
432                    return LockResult::Deadlock(holders.unwrap_or_default().into_iter().collect());
433                }
434
435                *write_unpoisoned(&self.wait_graph) = wait_graph;
436            }
437
438            // Check timeout
439            if start.elapsed() > timeout {
440                // Remove from wait queue
441                {
442                    let mut locks = write_unpoisoned(&self.locks);
443                    if let Some(lock) = locks.get_mut(&resource_key) {
444                        lock.waiters.retain(|w| w.txn_id != txn_id);
445                    }
446                }
447
448                // Remove from wait graph
449                if self.config.deadlock_detection {
450                    let mut wait_graph = write_unpoisoned(&self.wait_graph);
451                    wait_graph.remove(&txn_id);
452                }
453
454                let mut stats = write_unpoisoned(&self.stats);
455                stats.timeouts += 1;
456                stats.waiting -= 1;
457
458                return LockResult::Timeout;
459            }
460        }
461    }
462
463    /// Release a lock
464    pub fn release(&self, txn_id: TxnId, resource: &[u8]) -> bool {
465        let resource_key = resource.to_vec();
466
467        let granted = {
468            let mut locks = write_unpoisoned(&self.locks);
469
470            if let Some(lock) = locks.get_mut(&resource_key) {
471                if lock.release(txn_id).is_some() {
472                    // Remove from txn_locks
473                    let mut txn_locks = write_unpoisoned(&self.txn_locks);
474                    if let Some(resources) = txn_locks.get_mut(&txn_id) {
475                        resources.remove(&resource_key);
476                    }
477
478                    // Process waiters
479                    let granted = lock.process_waiters();
480
481                    // Update wait graph for granted transactions
482                    if self.config.deadlock_detection && !granted.is_empty() {
483                        let mut wait_graph = write_unpoisoned(&self.wait_graph);
484                        for txn in &granted {
485                            wait_graph.remove(txn);
486                        }
487                    }
488
489                    // Clean up empty lock
490                    if lock.holders.is_empty() && lock.waiters.is_empty() {
491                        locks.remove(&resource_key);
492                    }
493
494                    // Notify waiters
495                    self.waiter_cv.notify_all();
496
497                    return true;
498                }
499            }
500
501            false
502        };
503
504        granted
505    }
506
507    /// Release all locks for a transaction
508    pub fn release_all(&self, txn_id: TxnId) -> usize {
509        let resources: Vec<Vec<u8>> = {
510            let txn_locks = read_unpoisoned(&self.txn_locks);
511            txn_locks
512                .get(&txn_id)
513                .map(|r| r.iter().cloned().collect())
514                .unwrap_or_default()
515        };
516
517        let count = resources.len();
518
519        for resource in resources {
520            self.release(txn_id, &resource);
521        }
522
523        // Clean up txn_locks entry
524        {
525            let mut txn_locks = write_unpoisoned(&self.txn_locks);
526            txn_locks.remove(&txn_id);
527        }
528
529        // Clean up wait graph
530        if self.config.deadlock_detection {
531            let mut wait_graph = write_unpoisoned(&self.wait_graph);
532            wait_graph.remove(&txn_id);
533        }
534
535        count
536    }
537
538    /// Check if transaction holds lock on resource
539    pub fn holds_lock(&self, txn_id: TxnId, resource: &[u8]) -> Option<LockMode> {
540        let locks = read_unpoisoned(&self.locks);
541        locks
542            .get(resource)
543            .and_then(|lock| lock.holders.get(&txn_id).copied())
544    }
545
546    /// Get all locks held by transaction
547    pub fn get_locks(&self, txn_id: TxnId) -> Vec<(Vec<u8>, LockMode)> {
548        let txn_locks = read_unpoisoned(&self.txn_locks);
549        let locks = read_unpoisoned(&self.locks);
550
551        txn_locks
552            .get(&txn_id)
553            .map(|resources| {
554                resources
555                    .iter()
556                    .filter_map(|r| {
557                        locks
558                            .get(r)
559                            .and_then(|l| l.holders.get(&txn_id).map(|m| (r.clone(), *m)))
560                    })
561                    .collect()
562            })
563            .unwrap_or_default()
564    }
565
566    /// Detect deadlock using DFS on wait-for graph
567    fn detect_deadlock_inner(
568        &self,
569        start: TxnId,
570        wait_graph: &HashMap<TxnId, HashSet<TxnId>>,
571    ) -> bool {
572        let mut visited = HashSet::new();
573        let mut stack = HashSet::new();
574
575        Self::dfs_cycle(start, &mut visited, &mut stack, wait_graph)
576    }
577
578    fn build_wait_graph_from_locks(
579        locks: &HashMap<Vec<u8>, Lock>,
580    ) -> HashMap<TxnId, HashSet<TxnId>> {
581        let mut graph: HashMap<TxnId, HashSet<TxnId>> = HashMap::new();
582
583        for lock in locks.values() {
584            if lock.holders.is_empty() {
585                continue;
586            }
587            let holders: HashSet<TxnId> = lock.holders.keys().copied().collect();
588            for waiter in &lock.waiters {
589                graph
590                    .entry(waiter.txn_id)
591                    .or_default()
592                    .extend(holders.iter().copied());
593            }
594        }
595
596        graph
597    }
598
599    fn dfs_cycle(
600        node: TxnId,
601        visited: &mut HashSet<TxnId>,
602        stack: &mut HashSet<TxnId>,
603        wait_graph: &HashMap<TxnId, HashSet<TxnId>>,
604    ) -> bool {
605        if stack.contains(&node) {
606            return true; // Cycle found
607        }
608        if visited.contains(&node) {
609            return false; // Already processed
610        }
611
612        visited.insert(node);
613        stack.insert(node);
614
615        if let Some(waiting_for) = wait_graph.get(&node) {
616            for &next in waiting_for {
617                if Self::dfs_cycle(next, visited, stack, wait_graph) {
618                    return true;
619                }
620            }
621        }
622
623        stack.remove(&node);
624        false
625    }
626
627    /// Get statistics
628    pub fn stats(&self) -> LockStats {
629        read_unpoisoned(&self.stats).clone()
630    }
631
632    /// Get configuration
633    pub fn config(&self) -> &LockConfig {
634        &self.config
635    }
636}
637
638impl Default for LockManager {
639    fn default() -> Self {
640        Self::with_defaults()
641    }
642}
643
644#[cfg(test)]
645mod tests {
646    use super::*;
647
648    #[test]
649    fn test_lock_mode_compatibility() {
650        assert!(LockMode::Shared.is_compatible(&LockMode::Shared));
651        assert!(!LockMode::Shared.is_compatible(&LockMode::Exclusive));
652        assert!(!LockMode::Exclusive.is_compatible(&LockMode::Exclusive));
653        assert!(LockMode::IntentShared.is_compatible(&LockMode::IntentShared));
654    }
655
656    #[test]
657    fn test_lock_acquire_release() {
658        let lm = LockManager::with_defaults();
659
660        // Acquire shared lock
661        let result = lm.acquire(1, b"resource1", LockMode::Shared);
662        assert_eq!(result, LockResult::Granted);
663
664        // Another transaction can get shared lock
665        let result = lm.acquire(2, b"resource1", LockMode::Shared);
666        assert_eq!(result, LockResult::Granted);
667
668        // Release locks
669        assert!(lm.release(1, b"resource1"));
670        assert!(lm.release(2, b"resource1"));
671    }
672
673    #[test]
674    fn test_exclusive_lock() {
675        let lm = LockManager::with_defaults();
676
677        // Acquire exclusive lock
678        let result = lm.acquire(1, b"resource1", LockMode::Exclusive);
679        assert_eq!(result, LockResult::Granted);
680
681        // Check held
682        assert_eq!(lm.holds_lock(1, b"resource1"), Some(LockMode::Exclusive));
683
684        // Release
685        lm.release_all(1);
686        assert_eq!(lm.holds_lock(1, b"resource1"), None);
687    }
688
689    #[test]
690    fn test_release_all() {
691        let lm = LockManager::with_defaults();
692
693        // Acquire multiple locks
694        lm.acquire(1, b"r1", LockMode::Shared);
695        lm.acquire(1, b"r2", LockMode::Exclusive);
696        lm.acquire(1, b"r3", LockMode::Shared);
697
698        // Release all
699        let count = lm.release_all(1);
700        assert_eq!(count, 3);
701    }
702
703    #[test]
704    fn test_lock_limit_exceeded() {
705        let config = LockConfig {
706            max_locks_per_txn: 1,
707            ..LockConfig::default()
708        };
709        let lm = LockManager::new(config);
710
711        let result = lm.acquire(1, b"r1", LockMode::Shared);
712        assert_eq!(result, LockResult::Granted);
713
714        let result = lm.acquire(1, b"r2", LockMode::Shared);
715        assert_eq!(result, LockResult::LockLimitExceeded);
716    }
717
718    #[test]
719    fn test_lock_limit_allows_upgrade() {
720        let config = LockConfig {
721            max_locks_per_txn: 1,
722            ..LockConfig::default()
723        };
724        let lm = LockManager::new(config);
725
726        let result = lm.acquire(1, b"r1", LockMode::Shared);
727        assert_eq!(result, LockResult::Granted);
728
729        let result = lm.acquire(1, b"r1", LockMode::Exclusive);
730        assert_eq!(result, LockResult::Upgraded);
731    }
732
733    #[test]
734    fn test_get_locks() {
735        let lm = LockManager::with_defaults();
736
737        lm.acquire(1, b"r1", LockMode::Shared);
738        lm.acquire(1, b"r2", LockMode::Exclusive);
739
740        let locks = lm.get_locks(1);
741        assert_eq!(locks.len(), 2);
742    }
743
744    #[test]
745    fn test_waiter_timeout() {
746        let waiter = LockWaiter::new(1, LockMode::Shared, Duration::from_millis(1));
747        std::thread::sleep(Duration::from_millis(5));
748        assert!(waiter.is_timed_out());
749    }
750}