featherdb_mvcc/
wait_graph.rs1use featherdb_core::TransactionId;
7use std::collections::{HashMap, HashSet};
8
9pub struct WaitGraph {
14 edges: HashMap<TransactionId, HashSet<TransactionId>>,
16}
17
18impl WaitGraph {
19 pub fn new() -> Self {
21 Self {
22 edges: HashMap::new(),
23 }
24 }
25
26 pub fn add_wait(&mut self, waiter: TransactionId, holder: TransactionId) {
28 self.edges.entry(waiter).or_default().insert(holder);
29 }
30
31 pub fn remove_transaction(&mut self, txn: TransactionId) {
33 self.edges.remove(&txn);
35 for holders in self.edges.values_mut() {
37 holders.remove(&txn);
38 }
39 }
40
41 pub fn detect_cycle(&self) -> Option<Vec<TransactionId>> {
44 let mut visited = HashSet::new();
46 let mut rec_stack = HashSet::new();
47 let mut path = Vec::new();
48
49 for &start in self.edges.keys() {
50 if !visited.contains(&start) {
51 if let Some(cycle) =
52 self.dfs_find_cycle(start, &mut visited, &mut rec_stack, &mut path)
53 {
54 return Some(cycle);
55 }
56 }
57 }
58 None
59 }
60
61 fn dfs_find_cycle(
62 &self,
63 node: TransactionId,
64 visited: &mut HashSet<TransactionId>,
65 rec_stack: &mut HashSet<TransactionId>,
66 path: &mut Vec<TransactionId>,
67 ) -> Option<Vec<TransactionId>> {
68 visited.insert(node);
69 rec_stack.insert(node);
70 path.push(node);
71
72 if let Some(neighbors) = self.edges.get(&node) {
73 for &neighbor in neighbors {
74 if !visited.contains(&neighbor) {
75 if let Some(cycle) = self.dfs_find_cycle(neighbor, visited, rec_stack, path) {
76 return Some(cycle);
77 }
78 } else if rec_stack.contains(&neighbor) {
79 let cycle_start = path.iter().position(|&x| x == neighbor).unwrap();
81 return Some(path[cycle_start..].to_vec());
82 }
83 }
84 }
85
86 path.pop();
87 rec_stack.remove(&node);
88 None
89 }
90
91 pub fn select_victim(&self, cycle: &[TransactionId]) -> TransactionId {
93 *cycle.iter().max().unwrap()
94 }
95}
96
97impl Default for WaitGraph {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_no_cycle() {
109 let mut graph = WaitGraph::new();
110 graph.add_wait(TransactionId(1), TransactionId(2));
111 graph.add_wait(TransactionId(2), TransactionId(3));
112
113 assert!(graph.detect_cycle().is_none());
114 }
115
116 #[test]
117 fn test_simple_cycle() {
118 let mut graph = WaitGraph::new();
119 graph.add_wait(TransactionId(1), TransactionId(2));
121 graph.add_wait(TransactionId(2), TransactionId(1));
122
123 let cycle = graph.detect_cycle();
124 assert!(cycle.is_some());
125 let cycle = cycle.unwrap();
126 assert_eq!(cycle.len(), 2);
127 assert!(cycle.contains(&TransactionId(1)));
128 assert!(cycle.contains(&TransactionId(2)));
129 }
130
131 #[test]
132 fn test_complex_cycle() {
133 let mut graph = WaitGraph::new();
134 graph.add_wait(TransactionId(1), TransactionId(2));
136 graph.add_wait(TransactionId(2), TransactionId(3));
137 graph.add_wait(TransactionId(3), TransactionId(1));
138
139 let cycle = graph.detect_cycle();
140 assert!(cycle.is_some());
141 let cycle = cycle.unwrap();
142 assert_eq!(cycle.len(), 3);
143 assert!(cycle.contains(&TransactionId(1)));
144 assert!(cycle.contains(&TransactionId(2)));
145 assert!(cycle.contains(&TransactionId(3)));
146 }
147
148 #[test]
149 fn test_remove_transaction() {
150 let mut graph = WaitGraph::new();
151 graph.add_wait(TransactionId(1), TransactionId(2));
153 graph.add_wait(TransactionId(2), TransactionId(1));
154
155 assert!(graph.detect_cycle().is_some());
157
158 graph.remove_transaction(TransactionId(1));
160
161 assert!(graph.detect_cycle().is_none());
163 }
164
165 #[test]
166 fn test_remove_transaction_breaks_cycle() {
167 let mut graph = WaitGraph::new();
168 graph.add_wait(TransactionId(1), TransactionId(2));
170 graph.add_wait(TransactionId(2), TransactionId(3));
171 graph.add_wait(TransactionId(3), TransactionId(1));
172
173 assert!(graph.detect_cycle().is_some());
175
176 graph.remove_transaction(TransactionId(2));
178
179 assert!(graph.detect_cycle().is_none());
181 }
182
183 #[test]
184 fn test_victim_selection() {
185 let graph = WaitGraph::new();
186
187 let cycle = vec![TransactionId(1), TransactionId(2)];
189 assert_eq!(graph.select_victim(&cycle), TransactionId(2));
190
191 let cycle = vec![TransactionId(5), TransactionId(10), TransactionId(3)];
193 assert_eq!(graph.select_victim(&cycle), TransactionId(10));
194 }
195
196 #[test]
197 fn test_victim_selection_youngest() {
198 let graph = WaitGraph::new();
199
200 let cycle = vec![TransactionId(100), TransactionId(200), TransactionId(150)];
202 assert_eq!(graph.select_victim(&cycle), TransactionId(200));
203 }
204
205 #[test]
206 fn test_multiple_waiters() {
207 let mut graph = WaitGraph::new();
208 graph.add_wait(TransactionId(1), TransactionId(3));
210 graph.add_wait(TransactionId(2), TransactionId(3));
211
212 assert!(graph.detect_cycle().is_none());
214
215 graph.add_wait(TransactionId(3), TransactionId(1));
217
218 assert!(graph.detect_cycle().is_some());
220 }
221
222 #[test]
223 fn test_remove_holder_from_multiple_waiters() {
224 let mut graph = WaitGraph::new();
225 graph.add_wait(TransactionId(1), TransactionId(3));
227 graph.add_wait(TransactionId(2), TransactionId(3));
228 graph.add_wait(TransactionId(3), TransactionId(1));
229
230 assert!(graph.detect_cycle().is_some());
232
233 graph.remove_transaction(TransactionId(3));
235
236 assert!(graph.detect_cycle().is_none());
238 }
239
240 #[test]
241 fn test_empty_graph() {
242 let graph = WaitGraph::new();
243 assert!(graph.detect_cycle().is_none());
244 }
245
246 #[test]
247 fn test_single_node() {
248 let mut graph = WaitGraph::new();
249 graph.add_wait(TransactionId(1), TransactionId(1));
251
252 let cycle = graph.detect_cycle();
253 assert!(cycle.is_some());
254 let cycle = cycle.unwrap();
255 assert_eq!(cycle.len(), 1);
256 assert_eq!(cycle[0], TransactionId(1));
257 }
258
259 #[test]
260 fn test_multiple_disconnected_cycles() {
261 let mut graph = WaitGraph::new();
262 graph.add_wait(TransactionId(1), TransactionId(2));
264 graph.add_wait(TransactionId(2), TransactionId(1));
265
266 graph.add_wait(TransactionId(3), TransactionId(4));
268 graph.add_wait(TransactionId(4), TransactionId(3));
269
270 assert!(graph.detect_cycle().is_some());
272 }
273
274 #[test]
275 fn test_long_chain_no_cycle() {
276 let mut graph = WaitGraph::new();
277 graph.add_wait(TransactionId(1), TransactionId(2));
279 graph.add_wait(TransactionId(2), TransactionId(3));
280 graph.add_wait(TransactionId(3), TransactionId(4));
281 graph.add_wait(TransactionId(4), TransactionId(5));
282
283 assert!(graph.detect_cycle().is_none());
284 }
285
286 #[test]
287 fn test_diamond_pattern_no_cycle() {
288 let mut graph = WaitGraph::new();
289 graph.add_wait(TransactionId(1), TransactionId(2));
293 graph.add_wait(TransactionId(1), TransactionId(3));
294 graph.add_wait(TransactionId(2), TransactionId(4));
295 graph.add_wait(TransactionId(3), TransactionId(4));
296
297 assert!(graph.detect_cycle().is_none());
298 }
299}