Skip to main content

cynos_storage/
lock.rs

1//! Lock management for Cynos database.
2//!
3//! This module provides lock management for concurrent access control.
4
5use alloc::collections::BTreeMap;
6use alloc::collections::BTreeSet;
7use alloc::string::{String, ToString};
8use alloc::vec::Vec;
9use cynos_core::{Error, Result};
10
11/// Lock type.
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum LockType {
14    /// Shared lock (read).
15    Shared,
16    /// Exclusive lock (write).
17    Exclusive,
18}
19
20/// Lock request.
21#[derive(Clone, Debug)]
22pub struct LockRequest {
23    /// Transaction ID requesting the lock.
24    pub tx_id: u64,
25    /// Lock type.
26    pub lock_type: LockType,
27}
28
29/// Lock state for a resource.
30#[derive(Clone, Debug, Default)]
31struct LockState {
32    /// Transactions holding shared locks.
33    shared_holders: BTreeSet<u64>,
34    /// Transaction holding exclusive lock (if any).
35    exclusive_holder: Option<u64>,
36}
37
38impl LockState {
39    fn new() -> Self {
40        Self::default()
41    }
42
43    fn is_free(&self) -> bool {
44        self.shared_holders.is_empty() && self.exclusive_holder.is_none()
45    }
46
47    fn can_grant_shared(&self, tx_id: u64) -> bool {
48        // Can grant shared if no exclusive lock or we already hold it
49        self.exclusive_holder.is_none() || self.exclusive_holder == Some(tx_id)
50    }
51
52    fn can_grant_exclusive(&self, tx_id: u64) -> bool {
53        // Can grant exclusive if no locks or only we hold shared
54        (self.exclusive_holder.is_none() && self.shared_holders.is_empty())
55            || (self.exclusive_holder.is_none()
56                && self.shared_holders.len() == 1
57                && self.shared_holders.contains(&tx_id))
58            || self.exclusive_holder == Some(tx_id)
59    }
60}
61
62/// Lock manager for managing resource locks.
63pub struct LockManager {
64    /// Locks by resource name (table name).
65    locks: BTreeMap<String, LockState>,
66}
67
68impl LockManager {
69    /// Creates a new lock manager.
70    pub fn new() -> Self {
71        Self {
72            locks: BTreeMap::new(),
73        }
74    }
75
76    /// Acquires a lock on a resource.
77    pub fn acquire(&mut self, resource: &str, tx_id: u64, lock_type: LockType) -> Result<()> {
78        let state = self.locks.entry(resource.to_string()).or_insert_with(LockState::new);
79
80        match lock_type {
81            LockType::Shared => {
82                if state.can_grant_shared(tx_id) {
83                    state.shared_holders.insert(tx_id);
84                    Ok(())
85                } else {
86                    Err(Error::invalid_operation("Cannot acquire shared lock"))
87                }
88            }
89            LockType::Exclusive => {
90                if state.can_grant_exclusive(tx_id) {
91                    // Upgrade from shared if needed
92                    state.shared_holders.remove(&tx_id);
93                    state.exclusive_holder = Some(tx_id);
94                    Ok(())
95                } else {
96                    Err(Error::invalid_operation("Cannot acquire exclusive lock"))
97                }
98            }
99        }
100    }
101
102    /// Releases all locks held by a transaction.
103    pub fn release_all(&mut self, tx_id: u64) {
104        for state in self.locks.values_mut() {
105            state.shared_holders.remove(&tx_id);
106            if state.exclusive_holder == Some(tx_id) {
107                state.exclusive_holder = None;
108            }
109        }
110
111        // Clean up empty lock states
112        self.locks.retain(|_, state| !state.is_free());
113    }
114
115    /// Releases a specific lock.
116    pub fn release(&mut self, resource: &str, tx_id: u64) {
117        if let Some(state) = self.locks.get_mut(resource) {
118            state.shared_holders.remove(&tx_id);
119            if state.exclusive_holder == Some(tx_id) {
120                state.exclusive_holder = None;
121            }
122
123            if state.is_free() {
124                self.locks.remove(resource);
125            }
126        }
127    }
128
129    /// Checks if a transaction holds a lock on a resource.
130    pub fn holds_lock(&self, resource: &str, tx_id: u64) -> bool {
131        if let Some(state) = self.locks.get(resource) {
132            state.shared_holders.contains(&tx_id) || state.exclusive_holder == Some(tx_id)
133        } else {
134            false
135        }
136    }
137
138    /// Checks if a transaction holds an exclusive lock on a resource.
139    pub fn holds_exclusive(&self, resource: &str, tx_id: u64) -> bool {
140        if let Some(state) = self.locks.get(resource) {
141            state.exclusive_holder == Some(tx_id)
142        } else {
143            false
144        }
145    }
146
147    /// Returns all resources locked by a transaction.
148    pub fn get_locked_resources(&self, tx_id: u64) -> Vec<&str> {
149        self.locks
150            .iter()
151            .filter(|(_, state)| {
152                state.shared_holders.contains(&tx_id) || state.exclusive_holder == Some(tx_id)
153            })
154            .map(|(name, _)| name.as_str())
155            .collect()
156    }
157}
158
159impl Default for LockManager {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_acquire_shared_lock() {
171        let mut lm = LockManager::new();
172
173        assert!(lm.acquire("table1", 1, LockType::Shared).is_ok());
174        assert!(lm.holds_lock("table1", 1));
175    }
176
177    #[test]
178    fn test_acquire_exclusive_lock() {
179        let mut lm = LockManager::new();
180
181        assert!(lm.acquire("table1", 1, LockType::Exclusive).is_ok());
182        assert!(lm.holds_exclusive("table1", 1));
183    }
184
185    #[test]
186    fn test_multiple_shared_locks() {
187        let mut lm = LockManager::new();
188
189        assert!(lm.acquire("table1", 1, LockType::Shared).is_ok());
190        assert!(lm.acquire("table1", 2, LockType::Shared).is_ok());
191        assert!(lm.holds_lock("table1", 1));
192        assert!(lm.holds_lock("table1", 2));
193    }
194
195    #[test]
196    fn test_exclusive_blocks_shared() {
197        let mut lm = LockManager::new();
198
199        assert!(lm.acquire("table1", 1, LockType::Exclusive).is_ok());
200        assert!(lm.acquire("table1", 2, LockType::Shared).is_err());
201    }
202
203    #[test]
204    fn test_shared_blocks_exclusive() {
205        let mut lm = LockManager::new();
206
207        assert!(lm.acquire("table1", 1, LockType::Shared).is_ok());
208        assert!(lm.acquire("table1", 2, LockType::Exclusive).is_err());
209    }
210
211    #[test]
212    fn test_upgrade_shared_to_exclusive() {
213        let mut lm = LockManager::new();
214
215        assert!(lm.acquire("table1", 1, LockType::Shared).is_ok());
216        assert!(lm.acquire("table1", 1, LockType::Exclusive).is_ok());
217        assert!(lm.holds_exclusive("table1", 1));
218    }
219
220    #[test]
221    fn test_release_lock() {
222        let mut lm = LockManager::new();
223
224        lm.acquire("table1", 1, LockType::Exclusive).unwrap();
225        lm.release("table1", 1);
226
227        assert!(!lm.holds_lock("table1", 1));
228        assert!(lm.acquire("table1", 2, LockType::Exclusive).is_ok());
229    }
230
231    #[test]
232    fn test_release_all() {
233        let mut lm = LockManager::new();
234
235        lm.acquire("table1", 1, LockType::Shared).unwrap();
236        lm.acquire("table2", 1, LockType::Exclusive).unwrap();
237
238        lm.release_all(1);
239
240        assert!(!lm.holds_lock("table1", 1));
241        assert!(!lm.holds_lock("table2", 1));
242    }
243
244    #[test]
245    fn test_get_locked_resources() {
246        let mut lm = LockManager::new();
247
248        lm.acquire("table1", 1, LockType::Shared).unwrap();
249        lm.acquire("table2", 1, LockType::Exclusive).unwrap();
250        lm.acquire("table3", 2, LockType::Shared).unwrap();
251
252        let resources = lm.get_locked_resources(1);
253        assert_eq!(resources.len(), 2);
254        assert!(resources.contains(&"table1"));
255        assert!(resources.contains(&"table2"));
256    }
257}