dmsc/core/
lock.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Safe Lock Utilities
19//!
20//! This module provides safe abstractions for lock acquisition and management,
21//! eliminating the need for `.unwrap()` and `.expect()` calls when working with
22//! synchronization primitives. These utilities ensure robust error handling
23//! in concurrent scenarios without risking thread panics.
24//!
25//! ## Key Components
26//!
27//! - **DMSCLockError**: Specialized error type for lock-related failures
28//! - **DMSCLockResult**: Result type alias for lock operations
29//! - **RwLockExtensions**: Extension traits for standard `RwLock` types
30//! - **MutexExtensions**: Extension traits for standard `Mutex` types
31//!
32//! ## Design Principles
33//!
34//! 1. **Never Panic**: All lock operations return `Result` instead of panicking
35//! 2. **Poison Error Handling**: Properly handles poisoned locks without panicking
36//! 3. **Consistent API**: Uniform error handling across all lock types
37//! 4. **Zero-Cost Abstraction**: Extension traits add no overhead when unused
38//!
39//! ## Usage
40//!
41//! ```rust,ignore
42//! use std::sync::Arc;
43//! use dmsc::core::lock::{RwLockExtensions, DMSCLockResult};
44//!
45//! struct SharedState {
46//!     counter: u64,
47//! }
48//!
49//! impl SharedState {
50//!     fn increment(&mut self) {
51//!         self.counter += 1;
52//!     }
53//!
54//!     fn get_value(&self) -> u64 {
55//!         self.counter
56//!     }
57//! }
58//!
59//! fn example() -> DMSCLockResult<()> {
60//!     let state = Arc::new(RwLock::new(SharedState { counter: 0 }));
61//!
62//!     // Write lock with error handling
63//!     {
64//!         let mut guard = state.write_safe()?;
65//!         guard.increment();
66//!     }
67//!
68//!     // Read lock with error handling
69//!     {
70//!         let guard = state.read_safe()?;
71//!         assert_eq!(guard.get_value(), 1);
72//!     }
73//!
74//!     Ok(())
75//! }
76//! ```
77
78use std::sync::{RwLock, Mutex, PoisonError, RwLockReadGuard, RwLockWriteGuard, MutexGuard};
79use std::fmt;
80
81#[cfg(feature = "pyo3")]
82use pyo3::pyclass;
83
84/// Specialized error type for lock-related failures.
85///
86/// This error type provides detailed information about lock acquisition failures,
87/// including whether the lock was poisoned or simply contested. The error includes
88/// context about the lock's purpose for better debugging.
89#[derive(Debug, Clone, PartialEq, Eq, Hash)]
90#[cfg_attr(feature = "pyo3", pyclass)]
91pub struct DMSCLockError {
92    context: String,
93    is_poisoned: bool,
94}
95
96impl DMSCLockError {
97    /// Creates a new lock error with the given context.
98    ///
99    /// # Arguments
100    ///
101    ///     context: Human-readable description of what was being locked
102    ///
103    /// # Returns
104    ///
105    ///     A new `DMSCLockError` instance
106    pub fn new(context: &str) -> Self {
107        Self {
108            context: context.to_string(),
109            is_poisoned: false,
110        }
111    }
112
113    /// Creates a poisoned lock error.
114    ///
115    /// # Arguments
116    ///
117    ///     context: Human-readable description of what was being locked
118    ///
119    /// # Returns
120    ///
121    ///     A new `DMSCLockError` instance marked as poisoned
122    pub fn poisoned(context: &str) -> Self {
123        Self {
124            context: context.to_string(),
125            is_poisoned: true,
126        }
127    }
128
129    /// Gets the context message for this lock error.
130    pub fn get_context(&self) -> &str {
131        &self.context
132    }
133}
134
135impl fmt::Display for DMSCLockError {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        if self.is_poisoned {
138            write!(f, "Lock poisoned during acquisition: {}", self.context)
139        } else {
140            write!(f, "Lock acquisition failed: {}", self.context)
141        }
142    }
143}
144
145impl std::error::Error for DMSCLockError {}
146
147#[cfg(feature = "pyo3")]
148#[pyo3::prelude::pymethods]
149impl DMSCLockError {
150    #[new]
151    fn new_py(context: String, is_poisoned: bool) -> Self {
152        Self {
153            context,
154            is_poisoned,
155        }
156    }
157
158    #[staticmethod]
159    fn create_from_context(context: String) -> Self {
160        Self {
161            context,
162            is_poisoned: false,
163        }
164    }
165
166    #[staticmethod]
167    fn create_poisoned(context: String) -> Self {
168        Self {
169            context,
170            is_poisoned: true,
171        }
172    }
173
174    #[getter]
175    fn is_poisoned(&self) -> bool {
176        self.is_poisoned
177    }
178
179    #[getter]
180    fn context(&self) -> String {
181        self.context.clone()
182    }
183
184    fn __str__(&self) -> String {
185        self.to_string()
186    }
187
188    fn __repr__(&self) -> String {
189        format!("DMSCLockError {{ context: {:?}, is_poisoned: {} }}", self.context, self.is_poisoned)
190    }
191}
192
193/// Result type alias for lock operations.
194///
195/// This type alias simplifies error handling for lock-related operations,
196/// providing a consistent return type for all lock acquisitions.
197pub type DMSCLockResult<T> = Result<T, DMSCLockError>;
198
199/// Extension trait providing safe read lock acquisition for `RwLock`.
200///
201/// This trait adds a `read_safe` method to `RwLock` that returns a `Result`
202/// instead of panicking when the lock is poisoned or cannot be acquired.
203pub trait RwLockExtensions<T: Send + Sync> {
204    /// Acquires a read lock safely, returning a Result instead of panicking.
205    ///
206    /// This method attempts to acquire a read lock on the `RwLock`. If the lock
207    /// is held by a writer or if a previous holder panicked (poisoned), this
208    /// method returns an error instead of panicking.
209    ///
210    /// ## Arguments
211    ///
212    /// - `context`: A description of what is being locked for error messages
213    ///
214    /// ## Returns
215    ///
216    /// - `Ok(RwLockReadGuard<T>)` if the lock was acquired successfully
217    /// - `Err(DMSCLockError)` if the lock could not be acquired
218    ///
219    /// ## Examples
220    ///
221    /// ```rust,ignore
222    /// use std::sync::RwLock;
223    /// use dmsc::core::lock::RwLockExtensions;
224    ///
225    /// let lock = RwLock::new(42);
226    ///
227    /// match lock.read_safe("counter") {
228    ///     Ok(guard) => println!("Value: {}", *guard),
229    ///     Err(e) => println!("Failed to acquire lock: {}", e),
230    /// }
231    /// ```
232    fn read_safe(&self, context: &str) -> DMSCLockResult<RwLockReadGuard<'_, T>>;
233    
234    /// Acquires a write lock safely, returning a Result instead of panicking.
235    ///
236    /// This method attempts to acquire a write lock on the `RwLock`. If the lock
237    /// is currently held by any readers or if a previous holder panicked (poisoned),
238    /// this method returns an error instead of panicking.
239    ///
240    /// ## Arguments
241    ///
242    /// - `context`: A description of what is being locked for error messages
243    ///
244    /// ## Returns
245    ///
246    /// - `Ok(RwLockWriteGuard<T>)` if the lock was acquired successfully
247    /// - `Err(DMSCLockError)` if the lock could not be acquired
248    ///
249    /// ## Examples
250    ///
251    /// ```rust,ignore
252    /// use std::sync::RwLock;
253    /// use dmsc::core::lock::RwLockExtensions;
254    ///
255    /// let lock = RwLock::new(42);
256    ///
257    /// match lock.write_safe("counter") {
258    ///     Ok(mut guard) => {
259    ///         *guard += 1;
260    ///         println!("New value: {}", *guard);
261    ///     }
262    ///     Err(e) => println!("Failed to acquire lock: {}", e),
263    /// }
264    /// ```
265    fn write_safe(&self, context: &str) -> DMSCLockResult<RwLockWriteGuard<'_, T>>;
266    
267    /// Attempts to acquire a read lock, returning immediately if unavailable.
268    ///
269    /// This method tries to acquire a read lock without blocking. If the lock
270    /// is held by a writer, it returns an error immediately.
271    ///
272    /// ## Arguments
273    ///
274    /// - `context`: A description of what is being locked for error messages
275    ///
276    /// ## Returns
277    ///
278    /// - `Ok(Some(RwLockReadGuard<T>))` if the lock was acquired
279    /// - `Ok(None)` if the lock is held by a writer
280    /// - `Err(DMSCLockError)` if the lock is poisoned
281    fn try_read_safe(&self, context: &str) -> DMSCLockResult<Option<RwLockReadGuard<'_, T>>>;
282    
283    /// Attempts to acquire a write lock, returning immediately if unavailable.
284    ///
285    /// This method tries to acquire a write lock without blocking. If the lock
286    /// is held by any readers, it returns an error immediately.
287    ///
288    /// ## Arguments
289    ///
290    /// - `context`: A description of what is being locked for error messages
291    ///
292    /// ## Returns
293    ///
294    /// - `Ok(Some(RwLockWriteGuard<T>))` if the lock was acquired
295    /// - `Ok(None)` if the lock is held by readers or a writer
296    /// - `Err(DMSCLockError)` if the lock is poisoned
297    fn try_write_safe(&self, context: &str) -> DMSCLockResult<Option<RwLockWriteGuard<'_, T>>>;
298}
299
300impl<T: Send + Sync> RwLockExtensions<T> for RwLock<T> {
301    fn read_safe(&self, context: &str) -> DMSCLockResult<RwLockReadGuard<'_, T>> {
302        RwLock::read(self).map_err(|_| {
303            DMSCLockError::poisoned(context)
304        })
305    }
306    
307    fn write_safe(&self, context: &str) -> DMSCLockResult<RwLockWriteGuard<'_, T>> {
308        RwLock::write(self).map_err(|_| {
309            DMSCLockError::poisoned(context)
310        })
311    }
312    
313    fn try_read_safe(&self, context: &str) -> DMSCLockResult<Option<RwLockReadGuard<'_, T>>> {
314        match RwLock::try_read(self) {
315            Ok(guard) => Ok(Some(guard)),
316            Err(std::sync::TryLockError::Poisoned(_)) => {
317                Err(DMSCLockError::poisoned(context))
318            }
319            Err(std::sync::TryLockError::WouldBlock) => Ok(None),
320        }
321    }
322    
323    fn try_write_safe(&self, context: &str) -> DMSCLockResult<Option<RwLockWriteGuard<'_, T>>> {
324        match RwLock::try_write(self) {
325            Ok(guard) => Ok(Some(guard)),
326            Err(std::sync::TryLockError::Poisoned(_)) => {
327                Err(DMSCLockError::poisoned(context))
328            }
329            Err(std::sync::TryLockError::WouldBlock) => Ok(None),
330        }
331    }
332}
333
334/// Extension trait providing safe lock acquisition for `Mutex`.
335///
336/// This trait adds a `lock_safe` method to `Mutex` that returns a `Result`
337/// instead of panicking when the lock is poisoned.
338pub trait MutexExtensions<T: Send> {
339    /// Acquires a lock safely, returning a Result instead of panicking.
340    ///
341    /// This method attempts to acquire the mutex lock. If a previous holder
342    /// panicked while holding the lock (poisoned), this method returns an
343    /// error instead of panicking.
344    ///
345    /// ## Arguments
346    ///
347    /// - `context`: A description of what is being locked for error messages
348    ///
349    /// ## Returns
350    ///
351    /// - `Ok(MutexGuard<T>)` if the lock was acquired successfully
352    /// - `Err(DMSCLockError)` if the lock could not be acquired
353    ///
354    /// ## Examples
355    ///
356    /// ```rust,ignore
357    /// use std::sync::Mutex;
358    /// use dmsc::core::lock::MutexExtensions;
359    ///
360    /// let mutex = Mutex::new(42);
361    ///
362    /// match mutex.lock_safe("important_data") {
363    ///     Ok(guard) => println!("Value: {}", *guard),
364    ///     Err(e) => println!("Failed to acquire lock: {}", e),
365    /// }
366    /// ```
367    fn lock_safe(&self, context: &str) -> DMSCLockResult<MutexGuard<'_, T>>;
368    
369    /// Attempts to acquire the lock, returning immediately if unavailable.
370    ///
371    /// This method tries to acquire the mutex lock without blocking. If the
372    /// lock is held by another thread, it returns an error immediately.
373    ///
374    /// ## Arguments
375    ///
376    /// - `context`: A description of what is being locked for error messages
377    ///
378    /// ## Returns
379    ///
380    /// - `Ok(Some(MutexGuard<T>))` if the lock was acquired
381    /// - `Ok(None)` if the lock is held by another thread
382    /// - `Err(DMSCLockError)` if the lock is poisoned
383    fn try_lock_safe(&self, context: &str) -> DMSCLockResult<Option<MutexGuard<'_, T>>>;
384}
385
386impl<T: Send> MutexExtensions<T> for Mutex<T> {
387    fn lock_safe(&self, context: &str) -> DMSCLockResult<MutexGuard<'_, T>> {
388        Mutex::lock(self).map_err(|_| {
389            DMSCLockError::poisoned(context)
390        })
391    }
392    
393    fn try_lock_safe(&self, context: &str) -> DMSCLockResult<Option<MutexGuard<'_, T>>> {
394        match Mutex::try_lock(self) {
395            Ok(guard) => Ok(Some(guard)),
396            Err(std::sync::TryLockError::Poisoned(_)) => {
397                Err(DMSCLockError::poisoned(context))
398            }
399            Err(std::sync::TryLockError::WouldBlock) => Ok(None),
400        }
401    }
402}
403
404/// Utility function to convert from `PoisonError` to `DMSCLockError`.
405///
406/// This conversion is useful when working with standard library types that
407/// return `PoisonError` and you want to convert to our custom lock error type.
408///
409/// ## Arguments
410///
411/// - `error`: The poison error to convert
412/// - `context`: Description of what was being locked
413///
414/// ## Returns
415///
416/// A `DMSCLockError` with the appropriate context and poisoned flag
417pub fn from_poison_error<T>(_error: PoisonError<T>, context: &str) -> DMSCLockError {
418    DMSCLockError::poisoned(context)
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424    use std::sync::atomic::{AtomicU64, Ordering};
425    use std::sync::Arc;
426    use std::thread;
427
428    #[test]
429    fn test_read_safe_success() {
430        let lock = RwLock::new(42);
431        let result = lock.read_safe("test counter");
432        assert!(result.is_ok());
433        assert_eq!(*result.unwrap(), 42);
434    }
435
436    #[test]
437    fn test_write_safe_success() {
438        let lock = RwLock::new(42);
439        let result = lock.write_safe("test counter");
440        assert!(result.is_ok());
441        assert_eq!(*result.unwrap(), 42);
442    }
443
444    #[test]
445    fn test_try_read_safe_available() {
446        let lock = RwLock::new(42);
447        let result = lock.try_read_safe("test counter");
448        assert!(result.is_ok());
449        assert!(result.unwrap().is_some());
450    }
451
452    #[test]
453    fn test_try_write_safe_unavailable() {
454        let lock = RwLock::new(42);
455        let _write_guard = lock.write_safe("test counter").unwrap();
456        
457        let result = lock.try_write_safe("test counter");
458        assert!(result.is_ok());
459        assert!(result.unwrap().is_none());
460    }
461
462    #[test]
463    fn test_mutex_lock_safe() {
464        let mutex = Mutex::new(42);
465        let result = mutex.lock_safe("test mutex");
466        assert!(result.is_ok());
467        assert_eq!(*result.unwrap(), 42);
468    }
469
470    #[test]
471    fn test_lock_error_display() {
472        let error = DMSCLockError::new("test context");
473        assert_eq!(error.to_string(), "Lock acquisition failed: test context");
474        
475        let poisoned = DMSCLockError::poisoned("poisoned lock");
476        assert_eq!(poisoned.to_string(), "Lock poisoned during acquisition: poisoned lock");
477        assert!(poisoned.is_poisoned());
478    }
479
480    #[test]
481    fn test_concurrent_reads() {
482        let lock = Arc::new(RwLock::new(0));
483        let num_threads = 10;
484        let iterations = 1000;
485        
486        let handles: Vec<_> = (0..num_threads)
487            .map(|_| {
488                let lock = Arc::clone(&lock);
489                thread::spawn(move || {
490                    for _ in 0..iterations {
491                        let _guard = lock.read_safe("concurrent read").unwrap();
492                    }
493                })
494            })
495            .collect();
496        
497        for handle in handles {
498            handle.join().unwrap();
499        }
500        
501        assert_eq!(*lock.read_safe("final read").unwrap(), 0);
502    }
503
504    #[test]
505    fn test_concurrent_writes() {
506        let lock = Arc::new(RwLock::new(AtomicU64::new(0)));
507        let num_threads = 4;
508        let iterations = 100;
509        
510        let handles: Vec<_> = (0..num_threads)
511            .map(|_i| {
512                let lock = Arc::clone(&lock);
513                thread::spawn(move || {
514                    for _ in 0..iterations {
515                        let guard = lock.write_safe("concurrent write").unwrap();
516                        guard.fetch_add(1, Ordering::SeqCst);
517                    }
518                })
519            })
520            .collect();
521        
522        for handle in handles {
523            handle.join().unwrap();
524        }
525        
526        let final_value = lock.read_safe("final value").unwrap().load(Ordering::SeqCst);
527        assert_eq!(final_value, (num_threads * iterations) as u64);
528    }
529}