Skip to main content

waremax_map/
deadlock.rs

1//! Deadlock detection using Wait-For Graph
2//!
3//! Tracks which robots are waiting for which resources and detects
4//! circular wait conditions that would cause deadlocks.
5
6use std::collections::{HashMap, HashSet};
7use waremax_core::{EdgeId, NodeId, RobotId};
8
9/// Represents what a robot is waiting for
10#[derive(Clone, Debug)]
11pub enum WaitingFor {
12    /// Waiting for an edge, blocked by specific robots
13    Edge {
14        edge_id: EdgeId,
15        blocked_by: Vec<RobotId>,
16    },
17    /// Waiting for a node, blocked by specific robots
18    Node {
19        node_id: NodeId,
20        blocked_by: Vec<RobotId>,
21    },
22}
23
24impl WaitingFor {
25    /// Get the robots that are blocking this wait
26    pub fn blockers(&self) -> &[RobotId] {
27        match self {
28            WaitingFor::Edge { blocked_by, .. } => blocked_by,
29            WaitingFor::Node { blocked_by, .. } => blocked_by,
30        }
31    }
32}
33
34/// Wait-For Graph for deadlock detection
35///
36/// Maintains a graph where:
37/// - Nodes represent robots
38/// - Edges represent "waits-for" relationships
39///
40/// A cycle in this graph indicates a deadlock.
41#[derive(Clone, Debug, Default)]
42pub struct WaitForGraph {
43    /// Maps robot_id -> what it's waiting for (including which robots block it)
44    waiting_for: HashMap<RobotId, WaitingFor>,
45}
46
47impl WaitForGraph {
48    /// Create a new empty wait-for graph
49    pub fn new() -> Self {
50        Self {
51            waiting_for: HashMap::new(),
52        }
53    }
54
55    /// Record that a robot is waiting for a resource
56    pub fn add_wait(&mut self, robot: RobotId, waiting_for: WaitingFor) {
57        self.waiting_for.insert(robot, waiting_for);
58    }
59
60    /// Remove a robot's wait (e.g., when it acquires the resource)
61    pub fn remove_wait(&mut self, robot: RobotId) {
62        self.waiting_for.remove(&robot);
63    }
64
65    /// Check if a robot is currently waiting
66    pub fn is_waiting(&self, robot: RobotId) -> bool {
67        self.waiting_for.contains_key(&robot)
68    }
69
70    /// Get what a robot is waiting for, if anything
71    pub fn get_wait(&self, robot: RobotId) -> Option<&WaitingFor> {
72        self.waiting_for.get(&robot)
73    }
74
75    /// Get the number of robots currently waiting
76    pub fn waiting_count(&self) -> usize {
77        self.waiting_for.len()
78    }
79
80    /// Detect if there's a cycle (deadlock) in the wait-for graph
81    ///
82    /// Returns Some(cycle) with the robot IDs forming the cycle,
83    /// or None if no deadlock exists.
84    ///
85    /// Uses DFS-based cycle detection.
86    pub fn detect_cycle(&self) -> Option<Vec<RobotId>> {
87        // For each waiting robot, try to find a cycle starting from it
88        for &start_robot in self.waiting_for.keys() {
89            if let Some(cycle) = self.find_cycle_from(start_robot) {
90                return Some(cycle);
91            }
92        }
93        None
94    }
95
96    /// Try to find a cycle starting from a specific robot using DFS
97    fn find_cycle_from(&self, start: RobotId) -> Option<Vec<RobotId>> {
98        let mut visited = HashSet::new();
99        let mut path = Vec::new();
100        let mut path_set = HashSet::new();
101
102        self.dfs_find_cycle(start, &mut visited, &mut path, &mut path_set, start)
103    }
104
105    /// DFS helper for cycle detection
106    fn dfs_find_cycle(
107        &self,
108        current: RobotId,
109        visited: &mut HashSet<RobotId>,
110        path: &mut Vec<RobotId>,
111        path_set: &mut HashSet<RobotId>,
112        start: RobotId,
113    ) -> Option<Vec<RobotId>> {
114        // If we've already fully explored this robot, no cycle through it
115        if visited.contains(&current) {
116            return None;
117        }
118
119        // If current is in the current path, we found a cycle
120        if path_set.contains(&current) {
121            // Extract the cycle from the path
122            let cycle_start_idx = path.iter().position(|&r| r == current)?;
123            return Some(path[cycle_start_idx..].to_vec());
124        }
125
126        // Add current to path
127        path.push(current);
128        path_set.insert(current);
129
130        // Get what this robot is waiting for
131        if let Some(waiting) = self.waiting_for.get(&current) {
132            // For each robot that's blocking us, continue DFS
133            for &blocker in waiting.blockers() {
134                // Check if blocker leads back to start (simple cycle check)
135                if blocker == start && path.len() > 1 {
136                    path.push(start);
137                    return Some(path.clone());
138                }
139
140                // Continue DFS to see if blocker is also waiting
141                if self.waiting_for.contains_key(&blocker) {
142                    if let Some(cycle) =
143                        self.dfs_find_cycle(blocker, visited, path, path_set, start)
144                    {
145                        return Some(cycle);
146                    }
147                }
148            }
149        }
150
151        // Backtrack
152        path.pop();
153        path_set.remove(&current);
154        visited.insert(current);
155
156        None
157    }
158
159    /// Detect all cycles in the graph (for debugging/analysis)
160    pub fn detect_all_cycles(&self) -> Vec<Vec<RobotId>> {
161        let mut cycles = Vec::new();
162        let mut found_robots = HashSet::new();
163
164        for &start_robot in self.waiting_for.keys() {
165            if found_robots.contains(&start_robot) {
166                continue;
167            }
168
169            if let Some(cycle) = self.find_cycle_from(start_robot) {
170                for &robot in &cycle {
171                    found_robots.insert(robot);
172                }
173                cycles.push(cycle);
174            }
175        }
176
177        cycles
178    }
179
180    /// Clear all waits (e.g., at end of simulation)
181    pub fn clear(&mut self) {
182        self.waiting_for.clear();
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_empty_graph_no_cycle() {
192        let graph = WaitForGraph::new();
193        assert!(graph.detect_cycle().is_none());
194    }
195
196    #[test]
197    fn test_single_wait_no_cycle() {
198        let mut graph = WaitForGraph::new();
199        graph.add_wait(
200            RobotId(1),
201            WaitingFor::Edge {
202                edge_id: EdgeId(100),
203                blocked_by: vec![RobotId(2)],
204            },
205        );
206        // Robot 2 is not waiting, so no cycle
207        assert!(graph.detect_cycle().is_none());
208    }
209
210    #[test]
211    fn test_two_robot_cycle() {
212        let mut graph = WaitForGraph::new();
213        // Robot 1 waits for Robot 2
214        graph.add_wait(
215            RobotId(1),
216            WaitingFor::Edge {
217                edge_id: EdgeId(100),
218                blocked_by: vec![RobotId(2)],
219            },
220        );
221        // Robot 2 waits for Robot 1
222        graph.add_wait(
223            RobotId(2),
224            WaitingFor::Node {
225                node_id: NodeId(50),
226                blocked_by: vec![RobotId(1)],
227            },
228        );
229
230        let cycle = graph.detect_cycle();
231        assert!(cycle.is_some());
232        let cycle = cycle.unwrap();
233        assert!(cycle.contains(&RobotId(1)));
234        assert!(cycle.contains(&RobotId(2)));
235    }
236
237    #[test]
238    fn test_three_robot_cycle() {
239        let mut graph = WaitForGraph::new();
240        // Robot 1 -> Robot 2 -> Robot 3 -> Robot 1
241        graph.add_wait(
242            RobotId(1),
243            WaitingFor::Edge {
244                edge_id: EdgeId(100),
245                blocked_by: vec![RobotId(2)],
246            },
247        );
248        graph.add_wait(
249            RobotId(2),
250            WaitingFor::Edge {
251                edge_id: EdgeId(101),
252                blocked_by: vec![RobotId(3)],
253            },
254        );
255        graph.add_wait(
256            RobotId(3),
257            WaitingFor::Edge {
258                edge_id: EdgeId(102),
259                blocked_by: vec![RobotId(1)],
260            },
261        );
262
263        let cycle = graph.detect_cycle();
264        assert!(cycle.is_some());
265        let cycle = cycle.unwrap();
266        assert_eq!(cycle.len(), 4); // 1 -> 2 -> 3 -> 1
267    }
268
269    #[test]
270    fn test_chain_no_cycle() {
271        let mut graph = WaitForGraph::new();
272        // Robot 1 -> Robot 2 -> Robot 3 (Robot 3 not waiting)
273        graph.add_wait(
274            RobotId(1),
275            WaitingFor::Edge {
276                edge_id: EdgeId(100),
277                blocked_by: vec![RobotId(2)],
278            },
279        );
280        graph.add_wait(
281            RobotId(2),
282            WaitingFor::Edge {
283                edge_id: EdgeId(101),
284                blocked_by: vec![RobotId(3)],
285            },
286        );
287        // Robot 3 is not waiting for anything
288
289        assert!(graph.detect_cycle().is_none());
290    }
291
292    #[test]
293    fn test_remove_wait_breaks_cycle() {
294        let mut graph = WaitForGraph::new();
295        graph.add_wait(
296            RobotId(1),
297            WaitingFor::Edge {
298                edge_id: EdgeId(100),
299                blocked_by: vec![RobotId(2)],
300            },
301        );
302        graph.add_wait(
303            RobotId(2),
304            WaitingFor::Node {
305                node_id: NodeId(50),
306                blocked_by: vec![RobotId(1)],
307            },
308        );
309
310        // Cycle exists
311        assert!(graph.detect_cycle().is_some());
312
313        // Remove one wait
314        graph.remove_wait(RobotId(1));
315
316        // Cycle broken
317        assert!(graph.detect_cycle().is_none());
318    }
319
320    #[test]
321    fn test_multiple_blockers() {
322        let mut graph = WaitForGraph::new();
323        // Robot 1 is blocked by both Robot 2 and Robot 3
324        graph.add_wait(
325            RobotId(1),
326            WaitingFor::Edge {
327                edge_id: EdgeId(100),
328                blocked_by: vec![RobotId(2), RobotId(3)],
329            },
330        );
331        // Robot 2 waits for Robot 1 (creates cycle)
332        graph.add_wait(
333            RobotId(2),
334            WaitingFor::Node {
335                node_id: NodeId(50),
336                blocked_by: vec![RobotId(1)],
337            },
338        );
339        // Robot 3 is not waiting
340
341        let cycle = graph.detect_cycle();
342        assert!(cycle.is_some());
343    }
344
345    #[test]
346    fn test_is_waiting() {
347        let mut graph = WaitForGraph::new();
348        assert!(!graph.is_waiting(RobotId(1)));
349
350        graph.add_wait(
351            RobotId(1),
352            WaitingFor::Edge {
353                edge_id: EdgeId(100),
354                blocked_by: vec![RobotId(2)],
355            },
356        );
357
358        assert!(graph.is_waiting(RobotId(1)));
359        assert!(!graph.is_waiting(RobotId(2)));
360    }
361
362    #[test]
363    fn test_waiting_count() {
364        let mut graph = WaitForGraph::new();
365        assert_eq!(graph.waiting_count(), 0);
366
367        graph.add_wait(
368            RobotId(1),
369            WaitingFor::Edge {
370                edge_id: EdgeId(100),
371                blocked_by: vec![RobotId(2)],
372            },
373        );
374        assert_eq!(graph.waiting_count(), 1);
375
376        graph.add_wait(
377            RobotId(2),
378            WaitingFor::Node {
379                node_id: NodeId(50),
380                blocked_by: vec![RobotId(1)],
381            },
382        );
383        assert_eq!(graph.waiting_count(), 2);
384
385        graph.remove_wait(RobotId(1));
386        assert_eq!(graph.waiting_count(), 1);
387    }
388}