Skip to main content

rustfs_io_core/
deadlock_detector.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Deadlock detection for concurrent operations.
16//!
17//! This module provides deadlock detection mechanisms using wait-for graphs
18//! to identify potential circular dependencies between locks.
19
20use std::collections::{HashMap, HashSet};
21use std::sync::Mutex;
22use std::time::{Duration, Instant};
23
24/// Lock type identifier.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum LockType {
27    /// Mutex lock.
28    Mutex,
29    /// RwLock (read).
30    RwLockRead,
31    /// RwLock (write).
32    RwLockWrite,
33    /// Semaphore.
34    Semaphore,
35}
36
37impl LockType {
38    /// Get as string.
39    pub fn as_str(&self) -> &'static str {
40        match self {
41            LockType::Mutex => "mutex",
42            LockType::RwLockRead => "rwlock_read",
43            LockType::RwLockWrite => "rwlock_write",
44            LockType::Semaphore => "semaphore",
45        }
46    }
47}
48
49/// Lock information.
50#[derive(Debug, Clone)]
51pub struct LockInfo {
52    /// Lock ID.
53    pub id: u64,
54    /// Lock type.
55    pub lock_type: LockType,
56    /// Owner thread ID (if held).
57    pub owner: Option<u64>,
58    /// Waiters (thread IDs).
59    pub waiters: Vec<u64>,
60    /// Acquisition time.
61    pub acquired_at: Option<Instant>,
62}
63
64impl LockInfo {
65    /// Create new lock info.
66    pub fn new(id: u64, lock_type: LockType) -> Self {
67        Self {
68            id,
69            lock_type,
70            owner: None,
71            waiters: Vec::new(),
72            acquired_at: None,
73        }
74    }
75
76    /// Check if the lock is held.
77    pub fn is_held(&self) -> bool {
78        self.owner.is_some()
79    }
80
81    /// Get hold duration.
82    pub fn hold_duration(&self) -> Option<Duration> {
83        self.acquired_at.map(|t| t.elapsed())
84    }
85}
86
87/// Wait graph edge (thread A waits for thread B).
88#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89pub struct WaitGraphEdge {
90    /// Waiting thread ID.
91    pub waiter: u64,
92    /// Resource/thread being waited for.
93    pub waited_for: u64,
94    /// Lock ID involved.
95    pub lock_id: u64,
96}
97
98/// Deadlock detector configuration.
99#[derive(Debug, Clone)]
100pub struct DeadlockDetectorConfig {
101    /// Detection interval.
102    pub detection_interval: Duration,
103    /// Maximum lock hold time before warning.
104    pub max_hold_time: Duration,
105    /// Whether detection is enabled.
106    pub enabled: bool,
107}
108
109impl Default for DeadlockDetectorConfig {
110    fn default() -> Self {
111        Self {
112            detection_interval: Duration::from_secs(1),
113            max_hold_time: Duration::from_secs(30),
114            enabled: true,
115        }
116    }
117}
118
119/// Deadlock detector.
120/// Deadlock detector using wait-for graphs.
121///
122/// Uses `std::sync::Mutex` (not `tokio::sync::Mutex`) because:
123/// - Locks are never held across `.await` points
124/// - Critical sections are sub-microsecond (single HashMap operations)
125/// - `tokio::sync::Mutex` would add unnecessary overhead for these short operations
126pub struct DeadlockDetector {
127    /// Configuration.
128    config: DeadlockDetectorConfig,
129    /// Registered locks.
130    locks: Mutex<HashMap<u64, LockInfo>>,
131    /// Wait graph edges.
132    wait_graph: Mutex<Vec<WaitGraphEdge>>,
133    /// Tracked requests (request_id -> thread_id).
134    requests: Mutex<HashMap<String, u64>>,
135    /// Next lock ID.
136    next_lock_id: Mutex<u64>,
137}
138
139impl DeadlockDetector {
140    /// Create a new deadlock detector.
141    pub fn new(config: DeadlockDetectorConfig) -> Self {
142        Self {
143            config,
144            locks: Mutex::new(HashMap::new()),
145            wait_graph: Mutex::new(Vec::new()),
146            requests: Mutex::new(HashMap::new()),
147            next_lock_id: Mutex::new(0),
148        }
149    }
150
151    /// Create with default configuration.
152    pub fn with_defaults() -> Self {
153        Self::new(DeadlockDetectorConfig::default())
154    }
155
156    /// Get the configuration.
157    pub fn config(&self) -> &DeadlockDetectorConfig {
158        &self.config
159    }
160
161    /// Register a new lock.
162    pub fn register_lock(&self, lock_type: LockType) -> u64 {
163        let id = {
164            let mut next = self.next_lock_id.lock().unwrap_or_else(|e| e.into_inner());
165            *next += 1;
166            *next
167        };
168
169        let info = LockInfo::new(id, lock_type);
170        if let Ok(mut locks) = self.locks.lock() {
171            locks.insert(id, info);
172        }
173
174        id
175    }
176
177    /// Unregister a lock.
178    pub fn unregister_lock(&self, lock_id: u64) {
179        if let Ok(mut locks) = self.locks.lock() {
180            locks.remove(&lock_id);
181        }
182    }
183
184    /// Record lock acquisition.
185    pub fn record_acquire(&self, lock_id: u64, thread_id: u64) {
186        if !self.config.enabled {
187            return;
188        }
189
190        if let Ok(mut locks) = self.locks.lock()
191            && let Some(info) = locks.get_mut(&lock_id)
192        {
193            info.owner = Some(thread_id);
194            info.acquired_at = Some(Instant::now());
195            info.waiters.retain(|&w| w != thread_id);
196        }
197
198        // Remove wait edge
199        if let Ok(mut graph) = self.wait_graph.lock() {
200            graph.retain(|e| !(e.waiter == thread_id && e.lock_id == lock_id));
201        }
202    }
203
204    /// Record lock release.
205    pub fn record_release(&self, lock_id: u64) {
206        if !self.config.enabled {
207            return;
208        }
209
210        if let Ok(mut locks) = self.locks.lock()
211            && let Some(info) = locks.get_mut(&lock_id)
212        {
213            info.owner = None;
214            info.acquired_at = None;
215        }
216    }
217
218    /// Record a wait for lock.
219    pub fn record_wait(&self, lock_id: u64, thread_id: u64) {
220        if !self.config.enabled {
221            return;
222        }
223
224        // Add to waiters list
225        if let Ok(mut locks) = self.locks.lock()
226            && let Some(info) = locks.get_mut(&lock_id)
227        {
228            if !info.waiters.contains(&thread_id) {
229                info.waiters.push(thread_id);
230            }
231
232            // Add edge to wait graph
233            if let Some(owner) = info.owner
234                && owner != thread_id
235                && let Ok(mut graph) = self.wait_graph.lock()
236            {
237                graph.push(WaitGraphEdge {
238                    waiter: thread_id,
239                    waited_for: owner,
240                    lock_id,
241                });
242            }
243        }
244    }
245
246    /// Detect deadlocks using cycle detection in wait graph.
247    pub fn detect_deadlock(&self) -> Option<Vec<u64>> {
248        if !self.config.enabled {
249            return None;
250        }
251
252        let graph = match self.wait_graph.lock() {
253            Ok(g) => g,
254            Err(_) => return None,
255        };
256
257        // Build adjacency list
258        let mut adj: HashMap<u64, Vec<u64>> = HashMap::new();
259        for edge in graph.iter() {
260            adj.entry(edge.waiter).or_default().push(edge.waited_for);
261        }
262
263        // DFS for cycle detection
264        let mut visited: HashSet<u64> = HashSet::new();
265        let mut rec_stack: HashSet<u64> = HashSet::new();
266        let mut path: Vec<u64> = Vec::new();
267
268        for &node in adj.keys() {
269            if self.dfs_cycle(node, &adj, &mut visited, &mut rec_stack, &mut path) {
270                return Some(path);
271            }
272        }
273
274        None
275    }
276
277    /// DFS helper for cycle detection.
278    fn dfs_cycle(
279        &self,
280        node: u64,
281        adj: &HashMap<u64, Vec<u64>>,
282        visited: &mut HashSet<u64>,
283        rec_stack: &mut HashSet<u64>,
284        path: &mut Vec<u64>,
285    ) -> bool {
286        if rec_stack.contains(&node) {
287            // Found cycle, extract cycle from path
288            if let Some(start) = path.iter().position(|&n| n == node) {
289                *path = path[start..].to_vec();
290            }
291            path.push(node);
292            return true;
293        }
294
295        if visited.contains(&node) {
296            return false;
297        }
298
299        visited.insert(node);
300        rec_stack.insert(node);
301        path.push(node);
302
303        if let Some(neighbors) = adj.get(&node) {
304            for &neighbor in neighbors {
305                if self.dfs_cycle(neighbor, adj, visited, rec_stack, path) {
306                    return true;
307                }
308            }
309        }
310
311        rec_stack.remove(&node);
312        path.pop();
313        false
314    }
315
316    /// Check for long-held locks.
317    pub fn check_long_held(&self) -> Vec<(u64, Duration)> {
318        if !self.config.enabled {
319            return Vec::new();
320        }
321
322        let locks = match self.locks.lock() {
323            Ok(l) => l,
324            Err(_) => return Vec::new(),
325        };
326        let mut result = Vec::new();
327
328        for (&id, info) in locks.iter() {
329            if let Some(duration) = info.hold_duration()
330                && duration > self.config.max_hold_time
331            {
332                result.push((id, duration));
333            }
334        }
335
336        result
337    }
338
339    /// Register a request for tracking.
340    pub fn register_request(&self, request_id: &str, thread_id: u64) {
341        if let Ok(mut requests) = self.requests.lock() {
342            requests.insert(request_id.to_string(), thread_id);
343        }
344    }
345
346    /// Unregister a request.
347    pub fn unregister_request(&self, request_id: &str) {
348        if let Ok(mut requests) = self.requests.lock() {
349            requests.remove(request_id);
350        }
351    }
352
353    /// Get number of tracked requests.
354    pub fn tracked_count(&self) -> usize {
355        if let Ok(requests) = self.requests.lock() {
356            requests.len()
357        } else {
358            0
359        }
360    }
361
362    /// Get lock info.
363    pub fn get_lock_info(&self, lock_id: u64) -> Option<LockInfo> {
364        let locks = self.locks.lock().ok()?;
365        locks.get(&lock_id).cloned()
366    }
367
368    /// Get total number of registered locks.
369    pub fn lock_count(&self) -> usize {
370        let locks = self.locks.lock().unwrap_or_else(|e| e.into_inner());
371        locks.len()
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378
379    #[test]
380    fn test_lock_info() {
381        let info = LockInfo::new(1, LockType::Mutex);
382        assert!(!info.is_held());
383        assert!(info.hold_duration().is_none());
384    }
385
386    #[test]
387    fn test_register_lock() {
388        let detector = DeadlockDetector::with_defaults();
389
390        let id1 = detector.register_lock(LockType::Mutex);
391        let id2 = detector.register_lock(LockType::RwLockWrite);
392
393        assert_ne!(id1, id2);
394        assert_eq!(detector.lock_count(), 2);
395
396        detector.unregister_lock(id1);
397        assert_eq!(detector.lock_count(), 1);
398    }
399
400    #[test]
401    fn test_acquire_release() {
402        let detector = DeadlockDetector::with_defaults();
403        let lock_id = detector.register_lock(LockType::Mutex);
404
405        detector.record_acquire(lock_id, 1);
406        let info = detector.get_lock_info(lock_id).unwrap();
407        assert!(info.is_held());
408        assert_eq!(info.owner, Some(1));
409
410        detector.record_release(lock_id);
411        let info = detector.get_lock_info(lock_id).unwrap();
412        assert!(!info.is_held());
413    }
414
415    #[test]
416    fn test_request_tracking() {
417        let detector = DeadlockDetector::with_defaults();
418
419        detector.register_request("req-1", 1);
420        detector.register_request("req-2", 2);
421        assert_eq!(detector.tracked_count(), 2);
422
423        detector.unregister_request("req-1");
424        assert_eq!(detector.tracked_count(), 1);
425    }
426
427    #[test]
428    fn test_no_deadlock() {
429        let detector = DeadlockDetector::with_defaults();
430
431        let lock1 = detector.register_lock(LockType::Mutex);
432        let lock2 = detector.register_lock(LockType::Mutex);
433
434        // Thread 1 holds lock1, waits for lock2
435        detector.record_acquire(lock1, 1);
436        detector.record_wait(lock2, 1);
437
438        // Thread 2 holds lock2
439        detector.record_acquire(lock2, 2);
440
441        // No deadlock
442        assert!(detector.detect_deadlock().is_none());
443    }
444
445    #[test]
446    fn test_disabled_detector() {
447        let config = DeadlockDetectorConfig {
448            enabled: false,
449            ..Default::default()
450        };
451        let detector = DeadlockDetector::new(config);
452
453        let lock_id = detector.register_lock(LockType::Mutex);
454        detector.record_acquire(lock_id, 1);
455
456        // Should not track when disabled
457        assert!(detector.detect_deadlock().is_none());
458    }
459}