Skip to main content

qubit_lock/lock/
async_lock.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # Asynchronous Lock Trait
10//!
11//! Defines an asynchronous lock abstraction that supports acquiring
12//! locks without blocking threads.
13//!
14//! # Author
15//!
16//! Haixing Hu
17use std::future::Future;
18
19use tokio::sync::{
20    Mutex as AsyncMutex,
21    RwLock as AsyncRwLock,
22};
23
24use super::try_lock_error::TryLockError;
25
26/// Unified asynchronous lock trait
27///
28/// Provides a unified interface for different types of asynchronous
29/// locks, supporting both read and write operations. This trait allows
30/// locks to be used in async contexts through closures, avoiding the
31/// complexity of explicitly managing lock guards and their lifetimes.
32///
33/// # Design Philosophy
34///
35/// This trait unifies both exclusive async locks (like `tokio::sync::Mutex`)
36/// and read-write async locks (like `tokio::sync::RwLock`) under a single
37/// interface. The key insight is that all async locks can be viewed as
38/// supporting two operations:
39///
40/// - **Read operations**: Provide immutable access (`&T`) to the data
41/// - **Write operations**: Provide mutable access (`&mut T`) to the data
42///
43/// For exclusive async locks (Mutex), both read and write operations
44/// acquire the same exclusive lock, but the API clearly indicates the
45/// intended usage. For read-write async locks (RwLock), read operations
46/// use shared locks while write operations use exclusive locks.
47///
48/// This design enables:
49/// - Unified API across different async lock types
50/// - Clear semantic distinction between read and write operations
51/// - Generic async code that works with any lock type
52/// - Performance optimization through appropriate lock selection
53/// - Non-blocking async operations
54///
55/// Only lock acquisition is asynchronous. The closure passed to `read` or
56/// `write` executes synchronously while the guard is held, so it cannot
57/// `.await` and should not perform blocking or long-running work. Compute
58/// expensive values before acquiring the lock, or move blocking work to a
59/// dedicated blocking task and keep the locked closure short.
60///
61/// This crate enables only Tokio's `sync` feature for its normal dependency.
62/// Applications that create a Tokio runtime as shown in the examples must
63/// enable an appropriate Tokio runtime feature such as `rt` or
64/// `rt-multi-thread`.
65///
66/// # Performance Characteristics
67///
68/// Different async lock implementations have different performance
69/// characteristics:
70///
71/// ## Mutex-based async locks (ArcAsyncMutex, AsyncMutex)
72/// - `read`: Acquires exclusive lock, same performance as write
73/// - `write`: Acquires exclusive lock, same performance as read
74/// - **Use case**: When you need exclusive access or don't know access
75///   patterns
76///
77/// ## RwLock-based async locks (ArcAsyncRwLock, AsyncRwLock)
78/// - `read`: Acquires shared lock, allows concurrent readers
79/// - `write`: Acquires exclusive lock, blocks all other operations
80/// - **Use case**: Read-heavy async workloads where multiple readers can
81///   proceed concurrently
82///
83/// # Type Parameters
84///
85/// * `T` - The type of data protected by the lock
86///
87/// # Author
88///
89/// Haixing Hu
90pub trait AsyncLock<T: ?Sized> {
91    /// Acquires a read lock asynchronously and executes a closure
92    ///
93    /// This method awaits until a read lock can be acquired without
94    /// blocking the thread, then executes the provided closure with
95    /// immutable access to the protected data. For exclusive async
96    /// locks (Mutex), this acquires the same exclusive lock as write
97    /// operations. For read-write async locks (RwLock), this acquires
98    /// a shared lock allowing concurrent readers.
99    ///
100    /// # Use Cases
101    ///
102    /// - **Data inspection**: Reading values, checking state, validation
103    /// - **Read-only operations**: Computing derived values, formatting
104    ///   output
105    /// - **Condition checking**: Evaluating predicates without modification
106    /// - **Logging and debugging**: Accessing data for diagnostic purposes
107    ///
108    /// # Performance Notes
109    ///
110    /// - **Mutex-based async locks**: Same performance as write operations
111    /// - **RwLock-based async locks**: Allows concurrent readers, better
112    ///   for read-heavy async workloads
113    ///
114    /// # Arguments
115    ///
116    /// * `f` - Closure that receives an immutable reference (`&T`) to
117    ///   the protected data
118    ///
119    /// # Returns
120    ///
121    /// Returns a future that resolves to the result produced by the closure
122    ///
123    /// # Example
124    ///
125    /// ```rust
126    /// use qubit_lock::lock::{AsyncLock, ArcAsyncRwLock};
127    ///
128    /// let rt = tokio::runtime::Builder::new_current_thread()
129    ///     .enable_all()
130    ///     .build()
131    ///     .unwrap();
132    /// rt.block_on(async {
133    ///     let lock = ArcAsyncRwLock::new(vec![1, 2, 3]);
134    ///
135    ///     // Read operation - allows concurrent readers with RwLock
136    ///     let len = lock.read(|data| data.len()).await;
137    ///     assert_eq!(len, 3);
138    ///
139    ///     // Multiple concurrent readers possible with RwLock
140    ///     let sum = lock.read(|data|
141    ///         data.iter().sum::<i32>()
142    ///     ).await;
143    ///     assert_eq!(sum, 6);
144    /// });
145    /// ```
146    fn read<R, F>(&self, f: F) -> impl Future<Output = R> + Send
147    where
148        F: FnOnce(&T) -> R + Send,
149        R: Send;
150
151    /// Acquires a write lock asynchronously and executes a closure
152    ///
153    /// This method awaits until a write lock can be acquired without
154    /// blocking the thread, then executes the provided closure with
155    /// mutable access to the protected data. For all async lock types,
156    /// this acquires an exclusive lock that blocks all other operations
157    /// until the closure completes.
158    ///
159    /// # Use Cases
160    ///
161    /// - **Data modification**: Updating values, adding/removing elements
162    /// - **State changes**: Transitioning between different states
163    /// - **Initialization**: Setting up data structures
164    /// - **Cleanup operations**: Releasing resources, resetting state
165    ///
166    /// # Performance Notes
167    ///
168    /// - **All async lock types**: Exclusive access, blocks all other
169    ///   operations
170    /// - **RwLock advantage**: Only blocks during actual writes, not reads
171    ///
172    /// # Arguments
173    ///
174    /// * `f` - Closure that receives a mutable reference (`&mut T`) to
175    ///   the protected data
176    ///
177    /// # Returns
178    ///
179    /// Returns a future that resolves to the result produced by the closure
180    ///
181    /// # Example
182    ///
183    /// ```rust
184    /// use qubit_lock::lock::{AsyncLock, ArcAsyncRwLock};
185    ///
186    /// let rt = tokio::runtime::Builder::new_current_thread()
187    ///     .enable_all()
188    ///     .build()
189    ///     .unwrap();
190    /// rt.block_on(async {
191    ///     let lock = ArcAsyncRwLock::new(vec![1, 2, 3]);
192    ///
193    ///     // Write operation - exclusive access
194    ///     lock.write(|data| {
195    ///         data.push(4);
196    ///         data.sort();
197    ///     }).await;
198    ///
199    ///     // Verify the changes
200    ///     let result = lock.read(|data| data.clone()).await;
201    ///     assert_eq!(result, vec![1, 2, 3, 4]);
202    /// });
203    /// ```
204    fn write<R, F>(&self, f: F) -> impl Future<Output = R> + Send
205    where
206        F: FnOnce(&mut T) -> R + Send,
207        R: Send;
208
209    /// Attempts to acquire a read lock without waiting
210    ///
211    /// This method tries to acquire a read lock immediately. If the lock
212    /// cannot be acquired, it returns [`TryLockError::WouldBlock`] without
213    /// waiting. Otherwise, it executes the closure and returns `Ok` containing
214    /// the result.
215    ///
216    /// # Arguments
217    ///
218    /// * `f` - Closure that receives an immutable reference (`&T`) to
219    ///   the protected data if the lock is successfully acquired
220    ///
221    /// # Returns
222    ///
223    /// * `Ok(R)` - If the lock was acquired and closure executed
224    /// * `Err(TryLockError::WouldBlock)` - If the lock is currently unavailable
225    ///
226    /// # Errors
227    ///
228    /// Returns [`TryLockError::WouldBlock`] when the async lock cannot be
229    /// acquired immediately. Tokio locks are not poisoned, so this method does
230    /// not return [`TryLockError::Poisoned`].
231    ///
232    /// # Example
233    ///
234    /// ```rust
235    /// use qubit_lock::lock::{AsyncLock, ArcAsyncRwLock};
236    ///
237    /// let lock = ArcAsyncRwLock::new(42);
238    /// if let Ok(value) = lock.try_read(|data| *data) {
239    ///     println!("Got value: {}", value);
240    /// } else {
241    ///     println!("Lock is unavailable");
242    /// }
243    /// ```
244    fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
245    where
246        F: FnOnce(&T) -> R;
247
248    /// Attempts to acquire a write lock without waiting
249    ///
250    /// This method tries to acquire a write lock immediately. If the lock
251    /// is currently unavailable, it returns [`TryLockError::WouldBlock`]
252    /// without waiting. Otherwise, it executes the closure and returns `Ok`
253    /// containing the result.
254    ///
255    /// # Arguments
256    ///
257    /// * `f` - Closure that receives a mutable reference (`&mut T`) to
258    ///   the protected data if the lock is successfully acquired
259    ///
260    /// # Returns
261    ///
262    /// * `Ok(R)` - If the lock was acquired and closure executed
263    /// * `Err(TryLockError::WouldBlock)` - If the lock is currently unavailable
264    ///
265    /// # Errors
266    ///
267    /// Returns [`TryLockError::WouldBlock`] when the async lock cannot be
268    /// acquired immediately. Tokio locks are not poisoned, so this method does
269    /// not return [`TryLockError::Poisoned`].
270    ///
271    /// # Example
272    ///
273    /// ```rust
274    /// use qubit_lock::lock::{AsyncLock, ArcAsyncMutex};
275    ///
276    /// let lock = ArcAsyncMutex::new(42);
277    /// if let Ok(result) = lock.try_write(|data| {
278    ///     *data += 1;
279    ///     *data
280    /// }) {
281    ///     println!("New value: {}", result);
282    /// } else {
283    ///     println!("Lock is busy");
284    /// }
285    /// ```
286    fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
287    where
288        F: FnOnce(&mut T) -> R;
289}
290
291/// Asynchronous mutex implementation for tokio::sync::Mutex
292///
293/// This implementation uses Tokio's `Mutex` type to provide an
294/// asynchronous lock that can be awaited without blocking threads.
295/// Both read and write operations acquire the same exclusive lock,
296/// ensuring thread safety at the cost of concurrent access.
297///
298/// # Type Parameters
299///
300/// * `T` - The type of data protected by the lock
301///
302/// # Author
303///
304/// Haixing Hu
305impl<T: ?Sized + Send> AsyncLock<T> for AsyncMutex<T> {
306    /// Acquires the mutex and executes a read-only closure.
307    ///
308    /// # Arguments
309    ///
310    /// * `f` - Closure receiving immutable access to the protected value.
311    ///
312    /// # Returns
313    ///
314    /// A future resolving to the value returned by `f`.
315    #[inline]
316    async fn read<R, F>(&self, f: F) -> R
317    where
318        F: FnOnce(&T) -> R + Send,
319        R: Send,
320    {
321        let guard = self.lock().await;
322        f(&*guard)
323    }
324
325    /// Acquires the mutex and executes a mutable closure.
326    ///
327    /// # Arguments
328    ///
329    /// * `f` - Closure receiving mutable access to the protected value.
330    ///
331    /// # Returns
332    ///
333    /// A future resolving to the value returned by `f`.
334    #[inline]
335    async fn write<R, F>(&self, f: F) -> R
336    where
337        F: FnOnce(&mut T) -> R + Send,
338        R: Send,
339    {
340        let mut guard = self.lock().await;
341        f(&mut *guard)
342    }
343
344    /// Attempts to acquire the mutex without waiting for a read-only closure.
345    ///
346    /// # Arguments
347    ///
348    /// * `f` - Closure receiving immutable access when the mutex is acquired.
349    ///
350    /// # Returns
351    ///
352    /// `Ok(result)` if the mutex is acquired, or
353    /// [`TryLockError::WouldBlock`] if it is busy.
354    #[inline]
355    fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
356    where
357        F: FnOnce(&T) -> R,
358    {
359        self.try_lock()
360            .map(|guard| f(&*guard))
361            .map_err(|_| TryLockError::WouldBlock)
362    }
363
364    /// Attempts to acquire the mutex without waiting for a mutable closure.
365    ///
366    /// # Arguments
367    ///
368    /// * `f` - Closure receiving mutable access when the mutex is acquired.
369    ///
370    /// # Returns
371    ///
372    /// `Ok(result)` if the mutex is acquired, or
373    /// [`TryLockError::WouldBlock`] if it is busy.
374    #[inline]
375    fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
376    where
377        F: FnOnce(&mut T) -> R,
378    {
379        self.try_lock()
380            .map(|mut guard| f(&mut *guard))
381            .map_err(|_| TryLockError::WouldBlock)
382    }
383}
384
385/// Asynchronous read-write lock implementation for tokio::sync::RwLock
386///
387/// This implementation uses Tokio's `RwLock` type to provide an
388/// asynchronous read-write lock that supports multiple concurrent
389/// readers or a single writer without blocking threads. Read operations
390/// use shared locks allowing concurrent readers, while write operations
391/// use exclusive locks that block all other operations.
392///
393/// # Type Parameters
394///
395/// * `T` - The type of data protected by the lock
396///
397/// # Author
398///
399/// Haixing Hu
400impl<T: ?Sized + Send + Sync> AsyncLock<T> for AsyncRwLock<T> {
401    /// Acquires a shared read lock and executes a closure.
402    ///
403    /// # Arguments
404    ///
405    /// * `f` - Closure receiving immutable access to the protected value.
406    ///
407    /// # Returns
408    ///
409    /// A future resolving to the value returned by `f`.
410    #[inline]
411    async fn read<R, F>(&self, f: F) -> R
412    where
413        F: FnOnce(&T) -> R + Send,
414        R: Send,
415    {
416        let guard = self.read().await;
417        f(&*guard)
418    }
419
420    /// Acquires an exclusive write lock and executes a closure.
421    ///
422    /// # Arguments
423    ///
424    /// * `f` - Closure receiving mutable access to the protected value.
425    ///
426    /// # Returns
427    ///
428    /// A future resolving to the value returned by `f`.
429    #[inline]
430    async fn write<R, F>(&self, f: F) -> R
431    where
432        F: FnOnce(&mut T) -> R + Send,
433        R: Send,
434    {
435        let mut guard = self.write().await;
436        f(&mut *guard)
437    }
438
439    /// Attempts to acquire a shared read lock without waiting.
440    ///
441    /// # Arguments
442    ///
443    /// * `f` - Closure receiving immutable access when the read lock is
444    ///   acquired.
445    ///
446    /// # Returns
447    ///
448    /// `Ok(result)` if a read lock is acquired, or
449    /// [`TryLockError::WouldBlock`] if it is busy.
450    #[inline]
451    fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
452    where
453        F: FnOnce(&T) -> R,
454    {
455        self.try_read()
456            .map(|guard| f(&*guard))
457            .map_err(|_| TryLockError::WouldBlock)
458    }
459
460    /// Attempts to acquire an exclusive write lock without waiting.
461    ///
462    /// # Arguments
463    ///
464    /// * `f` - Closure receiving mutable access when the write lock is
465    ///   acquired.
466    ///
467    /// # Returns
468    ///
469    /// `Ok(result)` if a write lock is acquired, or
470    /// [`TryLockError::WouldBlock`] if it is busy.
471    #[inline]
472    fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
473    where
474        F: FnOnce(&mut T) -> R,
475    {
476        self.try_write()
477            .map(|mut guard| f(&mut *guard))
478            .map_err(|_| TryLockError::WouldBlock)
479    }
480}