qubit_lock/lock/lock.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # Lock Trait
11//!
12//! Defines an unified synchronous lock abstraction that supports acquiring locks
13//! and executing operations within the locked context. This trait allows locks to be
14//! used in a generic way through closures, avoiding the complexity of
15//! explicitly managing lock guards and their lifetimes.
16//!
17use std::sync::{
18 Mutex,
19 RwLock,
20};
21
22use parking_lot::Mutex as ParkingLotMutex;
23
24use super::try_lock_error::TryLockError;
25
26/// Unified synchronous lock trait
27///
28/// Provides a unified interface for different types of synchronous locks,
29/// supporting both read and write operations. This trait allows locks to be
30/// used in a generic way through closures, avoiding the complexity of
31/// explicitly managing lock guards and their lifetimes.
32///
33/// # Design Philosophy
34///
35/// This trait unifies both exclusive locks (like `Mutex`) and read-write
36/// locks (like `RwLock`) under a single interface. The key insight is that
37/// all locks can be viewed as supporting two operations:
38///
39/// - **Read operations**: Provide immutable access (`&T`) to the data
40/// - **Write operations**: Provide mutable access (`&mut T`) to the data
41///
42/// For exclusive locks (Mutex), both read and write operations acquire the
43/// same exclusive lock, but the API clearly indicates the intended usage.
44/// For read-write locks (RwLock), read operations use shared locks while
45/// write operations use exclusive locks.
46///
47/// This design enables:
48/// - Unified API across different lock types
49/// - Clear semantic distinction between read and write operations
50/// - Generic code that works with any lock type
51/// - Performance optimization through appropriate lock selection
52///
53/// # Performance Characteristics
54///
55/// Different lock implementations have different performance characteristics:
56///
57/// ## Mutex-based locks (ArcMutex, Mutex)
58/// - `read`: Acquires exclusive lock, same performance as write
59/// - `write`: Acquires exclusive lock, same performance as read
60/// - **Use case**: When you need exclusive access or don't know access patterns
61///
62/// ## RwLock-based locks (ArcRwLock, RwLock)
63/// - `read`: Acquires shared lock, allows concurrent readers
64/// - `write`: Acquires exclusive lock, blocks all other operations
65/// - **Use case**: Read-heavy workloads where multiple readers can proceed
66/// concurrently
67///
68/// # Type Parameters
69///
70/// * `T` - The type of data protected by the lock
71///
72pub trait Lock<T: ?Sized> {
73 /// Acquires a read lock and executes a closure
74 ///
75 /// This method provides immutable access to the protected data. It ensures
76 /// proper memory barriers are established:
77 ///
78 /// - **Acquire semantics**: Ensures that all subsequent memory operations
79 /// see the effects of previous operations released by the lock release.
80 /// - **Release semantics**: Ensures that all previous memory operations are
81 /// visible to subsequent lock acquisitions when the lock is released.
82 ///
83 /// For exclusive locks (Mutex), this acquires the same exclusive lock as
84 /// write operations. For read-write locks (RwLock), this acquires a
85 /// shared lock allowing concurrent readers.
86 ///
87 /// # Use Cases
88 ///
89 /// - **Data inspection**: Reading values, checking state, validation
90 /// - **Read-only operations**: Computing derived values, formatting output
91 /// - **Condition checking**: Evaluating predicates without modification
92 /// - **Logging and debugging**: Accessing data for diagnostic purposes
93 ///
94 /// # Performance Notes
95 ///
96 /// - **Mutex-based locks**: Same performance as write operations
97 /// - **RwLock-based locks**: Allows concurrent readers, better for
98 /// read-heavy workloads
99 ///
100 /// # Arguments
101 ///
102 /// * `f` - Closure that receives an immutable reference (`&T`) to the
103 /// protected data
104 ///
105 /// # Returns
106 ///
107 /// Returns the result produced by the closure
108 ///
109 /// # Example
110 ///
111 /// ```rust
112 /// use qubit_lock::lock::{Lock, ArcRwLock};
113 ///
114 /// let lock = ArcRwLock::new(vec![1, 2, 3]);
115 ///
116 /// // Read operation - allows concurrent readers with RwLock
117 /// let len = lock.read(|data| data.len());
118 /// assert_eq!(len, 3);
119 ///
120 /// // Multiple concurrent readers possible with RwLock
121 /// let sum = lock.read(|data| data.iter().sum::<i32>());
122 /// assert_eq!(sum, 6);
123 /// ```
124 fn read<R, F>(&self, f: F) -> R
125 where
126 F: FnOnce(&T) -> R;
127
128 /// Acquires a write lock and executes a closure
129 ///
130 /// This method provides mutable access to the protected data. It ensures
131 /// proper memory barriers are established:
132 ///
133 /// - **Acquire semantics**: Ensures that all subsequent memory operations
134 /// see the effects of previous operations released by the lock release.
135 /// - **Release semantics**: Ensures that all previous memory operations are
136 /// visible to subsequent lock acquisitions when the lock is released.
137 ///
138 /// For all lock types, this acquires an exclusive lock that blocks all
139 /// other operations until the closure completes.
140 ///
141 /// # Use Cases
142 ///
143 /// - **Data modification**: Updating values, adding/removing elements
144 /// - **State changes**: Transitioning between different states
145 /// - **Initialization**: Setting up data structures
146 /// - **Cleanup operations**: Releasing resources, resetting state
147 ///
148 /// # Performance Notes
149 ///
150 /// - **All lock types**: Exclusive access, blocks all other operations
151 /// - **RwLock advantage**: Only blocks during actual writes, not reads
152 ///
153 /// # Arguments
154 ///
155 /// * `f` - Closure that receives a mutable reference (`&mut T`) to the
156 /// protected data
157 ///
158 /// # Returns
159 ///
160 /// Returns the result produced by the closure
161 ///
162 /// # Example
163 ///
164 /// ```rust
165 /// use qubit_lock::lock::{Lock, ArcRwLock};
166 ///
167 /// let lock = ArcRwLock::new(vec![1, 2, 3]);
168 ///
169 /// // Write operation - exclusive access
170 /// lock.write(|data| {
171 /// data.push(4);
172 /// data.sort();
173 /// });
174 ///
175 /// // Verify the changes
176 /// let result = lock.read(|data| data.clone());
177 /// assert_eq!(result, vec![1, 2, 3, 4]);
178 /// ```
179 fn write<R, F>(&self, f: F) -> R
180 where
181 F: FnOnce(&mut T) -> R;
182
183 /// Attempts to acquire a read lock without blocking
184 ///
185 /// This method tries to acquire a read lock immediately. If the lock
186 /// cannot be acquired, it returns a detailed error. Otherwise, it executes
187 /// the closure and returns `Ok` containing the result.
188 ///
189 /// # Arguments
190 ///
191 /// * `f` - Closure that receives an immutable reference (`&T`) to the
192 /// protected data if the lock is successfully acquired
193 ///
194 /// # Returns
195 ///
196 /// * `Ok(R)` - If the lock was acquired and closure executed
197 /// * `Err(TryLockError::WouldBlock)` - If the lock is currently unavailable
198 /// * `Err(TryLockError::Poisoned)` - If the lock is poisoned
199 ///
200 /// # Errors
201 ///
202 /// Returns [`TryLockError::WouldBlock`] when the lock cannot be acquired
203 /// immediately. Returns [`TryLockError::Poisoned`] for standard-library
204 /// locks that were poisoned by a panic.
205 ///
206 /// # Example
207 ///
208 /// ```rust
209 /// use qubit_lock::lock::{Lock, ArcRwLock};
210 ///
211 /// let lock = ArcRwLock::new(42);
212 /// if let Ok(value) = lock.try_read(|data| *data) {
213 /// println!("Got value: {}", value);
214 /// } else {
215 /// println!("Lock is unavailable");
216 /// }
217 /// ```
218 fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
219 where
220 F: FnOnce(&T) -> R;
221
222 /// Attempts to acquire a write lock without blocking
223 ///
224 /// This method tries to acquire a write lock immediately. If the lock
225 /// cannot be acquired, it returns a detailed error. Otherwise, it executes
226 /// the closure and returns `Ok` containing the result.
227 ///
228 /// # Arguments
229 ///
230 /// * `f` - Closure that receives a mutable reference (`&mut T`) to the
231 /// protected data if the lock is successfully acquired
232 ///
233 /// # Returns
234 ///
235 /// * `Ok(R)` - If the lock was acquired and closure executed
236 /// * `Err(TryLockError::WouldBlock)` - If the lock is currently unavailable
237 /// * `Err(TryLockError::Poisoned)` - If the lock is poisoned
238 ///
239 /// # Errors
240 ///
241 /// Returns [`TryLockError::WouldBlock`] when the lock cannot be acquired
242 /// immediately. Returns [`TryLockError::Poisoned`] for standard-library
243 /// locks that were poisoned by a panic.
244 ///
245 /// # Example
246 ///
247 /// ```rust
248 /// use qubit_lock::lock::{Lock, ArcMutex};
249 ///
250 /// let lock = ArcMutex::new(42);
251 /// if let Ok(result) = lock.try_write(|data| {
252 /// *data += 1;
253 /// *data
254 /// }) {
255 /// println!("New value: {}", result);
256 /// } else {
257 /// println!("Lock is unavailable");
258 /// }
259 /// ```
260 fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
261 where
262 F: FnOnce(&mut T) -> R;
263}
264
265/// Synchronous mutex implementation of the Lock trait
266///
267/// This implementation uses the standard library's `Mutex` type to provide
268/// a synchronous lock. Both read and write operations acquire the same
269/// exclusive lock, ensuring thread safety at the cost of concurrent access.
270///
271/// # Type Parameters
272///
273/// * `T` - The type of data protected by the lock
274///
275impl<T: ?Sized> Lock<T> for Mutex<T> {
276 /// Acquires the mutex and executes a read-only closure.
277 ///
278 /// # Arguments
279 ///
280 /// * `f` - Closure receiving immutable access to the protected value.
281 ///
282 /// # Returns
283 ///
284 /// The value returned by `f`.
285 ///
286 /// # Panics
287 ///
288 /// Panics if the mutex is poisoned.
289 #[inline]
290 fn read<R, F>(&self, f: F) -> R
291 where
292 F: FnOnce(&T) -> R,
293 {
294 let guard = self.lock().unwrap();
295 f(&*guard)
296 }
297
298 /// Acquires the mutex and executes a mutable closure.
299 ///
300 /// # Arguments
301 ///
302 /// * `f` - Closure receiving mutable access to the protected value.
303 ///
304 /// # Returns
305 ///
306 /// The value returned by `f`.
307 ///
308 /// # Panics
309 ///
310 /// Panics if the mutex is poisoned.
311 #[inline]
312 fn write<R, F>(&self, f: F) -> R
313 where
314 F: FnOnce(&mut T) -> R,
315 {
316 let mut guard = self.lock().unwrap();
317 f(&mut *guard)
318 }
319
320 /// Attempts to acquire the mutex without blocking for a read-only closure.
321 ///
322 /// # Arguments
323 ///
324 /// * `f` - Closure receiving immutable access when the mutex is acquired.
325 ///
326 /// # Returns
327 ///
328 /// `Ok(result)` if the mutex is acquired.
329 ///
330 /// # Errors
331 ///
332 /// Returns [`TryLockError::WouldBlock`] when the mutex is held by another
333 /// thread, or [`TryLockError::Poisoned`] when the mutex is poisoned.
334 #[inline]
335 fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
336 where
337 F: FnOnce(&T) -> R,
338 {
339 match self.try_lock() {
340 Ok(guard) => Ok(f(&*guard)),
341 Err(std::sync::TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
342 Err(std::sync::TryLockError::Poisoned(_)) => Err(TryLockError::Poisoned),
343 }
344 }
345
346 /// Attempts to acquire the mutex without blocking for a mutable closure.
347 ///
348 /// # Arguments
349 ///
350 /// * `f` - Closure receiving mutable access when the mutex is acquired.
351 ///
352 /// # Returns
353 ///
354 /// `Ok(result)` if the mutex is acquired.
355 ///
356 /// # Errors
357 ///
358 /// Returns [`TryLockError::WouldBlock`] when the mutex is held by another
359 /// thread, or [`TryLockError::Poisoned`] when the mutex is poisoned.
360 #[inline]
361 fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
362 where
363 F: FnOnce(&mut T) -> R,
364 {
365 match self.try_lock() {
366 Ok(mut guard) => Ok(f(&mut *guard)),
367 Err(std::sync::TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
368 Err(std::sync::TryLockError::Poisoned(_)) => Err(TryLockError::Poisoned),
369 }
370 }
371}
372
373/// Synchronous read-write lock implementation of the Lock trait
374///
375/// This implementation uses the standard library's `RwLock` type to provide
376/// a synchronous read-write lock. Read operations use shared locks allowing
377/// concurrent readers, while write operations use exclusive locks that
378/// block all other operations.
379///
380/// # Type Parameters
381///
382/// * `T` - The type of data protected by the lock
383///
384impl<T: ?Sized> Lock<T> for RwLock<T> {
385 /// Acquires a shared read lock and executes a closure.
386 ///
387 /// # Arguments
388 ///
389 /// * `f` - Closure receiving immutable access to the protected value.
390 ///
391 /// # Returns
392 ///
393 /// The value returned by `f`.
394 ///
395 /// # Panics
396 ///
397 /// Panics if the read-write lock is poisoned.
398 #[inline]
399 fn read<R, F>(&self, f: F) -> R
400 where
401 F: FnOnce(&T) -> R,
402 {
403 let guard = self.read().unwrap();
404 f(&*guard)
405 }
406
407 /// Acquires an exclusive write lock and executes a closure.
408 ///
409 /// # Arguments
410 ///
411 /// * `f` - Closure receiving mutable access to the protected value.
412 ///
413 /// # Returns
414 ///
415 /// The value returned by `f`.
416 ///
417 /// # Panics
418 ///
419 /// Panics if the read-write lock is poisoned.
420 #[inline]
421 fn write<R, F>(&self, f: F) -> R
422 where
423 F: FnOnce(&mut T) -> R,
424 {
425 let mut guard = self.write().unwrap();
426 f(&mut *guard)
427 }
428
429 /// Attempts to acquire a shared read lock without blocking.
430 ///
431 /// # Arguments
432 ///
433 /// * `f` - Closure receiving immutable access when a read lock is acquired.
434 ///
435 /// # Returns
436 ///
437 /// `Ok(result)` if a read lock is acquired.
438 ///
439 /// # Errors
440 ///
441 /// Returns [`TryLockError::WouldBlock`] when the lock is unavailable, or
442 /// [`TryLockError::Poisoned`] when the lock is poisoned.
443 #[inline]
444 fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
445 where
446 F: FnOnce(&T) -> R,
447 {
448 match self.try_read() {
449 Ok(guard) => Ok(f(&*guard)),
450 Err(std::sync::TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
451 Err(std::sync::TryLockError::Poisoned(_)) => Err(TryLockError::Poisoned),
452 }
453 }
454
455 /// Attempts to acquire an exclusive write lock without blocking.
456 ///
457 /// # Arguments
458 ///
459 /// * `f` - Closure receiving mutable access when a write lock is acquired.
460 ///
461 /// # Returns
462 ///
463 /// `Ok(result)` if a write lock is acquired.
464 ///
465 /// # Errors
466 ///
467 /// Returns [`TryLockError::WouldBlock`] when the lock is unavailable, or
468 /// [`TryLockError::Poisoned`] when the lock is poisoned.
469 #[inline]
470 fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
471 where
472 F: FnOnce(&mut T) -> R,
473 {
474 match self.try_write() {
475 Ok(mut guard) => Ok(f(&mut *guard)),
476 Err(std::sync::TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
477 Err(std::sync::TryLockError::Poisoned(_)) => Err(TryLockError::Poisoned),
478 }
479 }
480}
481
482/// High-performance synchronous mutex implementation of the Lock trait
483///
484/// This implementation uses the `parking_lot` crate's `Mutex` type to provide
485/// a high-performance synchronous lock. Both read and write operations acquire
486/// the same exclusive lock, ensuring thread safety with better performance
487/// than the standard library's Mutex.
488///
489/// # Type Parameters
490///
491/// * `T` - The type of data protected by the lock
492///
493/// # Performance Characteristics
494///
495/// The parking_lot Mutex is generally faster than std::sync::Mutex due to:
496/// - More efficient lock acquisition and release
497/// - Better handling of contended locks
498/// - Reduced memory overhead
499/// - No risk of lock poisoning (panics don't poison the lock)
500///
501impl<T: ?Sized> Lock<T> for ParkingLotMutex<T> {
502 /// Acquires the parking_lot mutex and executes a read-only closure.
503 ///
504 /// # Arguments
505 ///
506 /// * `f` - Closure receiving immutable access to the protected value.
507 ///
508 /// # Returns
509 ///
510 /// The value returned by `f`.
511 #[inline]
512 fn read<R, F>(&self, f: F) -> R
513 where
514 F: FnOnce(&T) -> R,
515 {
516 let guard = self.lock();
517 f(&*guard)
518 }
519
520 /// Acquires the parking_lot mutex and executes a mutable closure.
521 ///
522 /// # Arguments
523 ///
524 /// * `f` - Closure receiving mutable access to the protected value.
525 ///
526 /// # Returns
527 ///
528 /// The value returned by `f`.
529 #[inline]
530 fn write<R, F>(&self, f: F) -> R
531 where
532 F: FnOnce(&mut T) -> R,
533 {
534 let mut guard = self.lock();
535 f(&mut *guard)
536 }
537
538 /// Attempts to acquire the parking_lot mutex without blocking for reading.
539 ///
540 /// # Arguments
541 ///
542 /// * `f` - Closure receiving immutable access when the mutex is acquired.
543 ///
544 /// # Returns
545 ///
546 /// `Ok(result)` if the mutex is acquired.
547 ///
548 /// # Errors
549 ///
550 /// Returns [`TryLockError::WouldBlock`] when the mutex is held by another
551 /// thread. parking_lot mutexes are not poisoned.
552 #[inline]
553 fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
554 where
555 F: FnOnce(&T) -> R,
556 {
557 self.try_lock()
558 .map(|guard| f(&*guard))
559 .ok_or(TryLockError::WouldBlock)
560 }
561
562 /// Attempts to acquire the parking_lot mutex without blocking for writing.
563 ///
564 /// # Arguments
565 ///
566 /// * `f` - Closure receiving mutable access when the mutex is acquired.
567 ///
568 /// # Returns
569 ///
570 /// `Ok(result)` if the mutex is acquired.
571 ///
572 /// # Errors
573 ///
574 /// Returns [`TryLockError::WouldBlock`] when the mutex is held by another
575 /// thread. parking_lot mutexes are not poisoned.
576 #[inline]
577 fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
578 where
579 F: FnOnce(&mut T) -> R,
580 {
581 self.try_lock()
582 .map(|mut guard| f(&mut *guard))
583 .ok_or(TryLockError::WouldBlock)
584 }
585}