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}