Skip to main content

qubit_lock/lock/
async_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//! # Asynchronous Lock Trait
11//!
12//! Defines an asynchronous lock abstraction that supports acquiring
13//! locks without blocking threads.
14//!
15use std::future::Future;
16
17use tokio::sync::{
18    Mutex as AsyncMutex,
19    RwLock as AsyncRwLock,
20};
21
22use super::try_lock_error::TryLockError;
23
24/// Unified asynchronous lock trait
25///
26/// Provides a unified interface for different types of asynchronous
27/// locks, supporting both read and write operations. This trait allows
28/// locks to be used in async contexts through closures, avoiding the
29/// complexity of explicitly managing lock guards and their lifetimes.
30///
31/// # Design Philosophy
32///
33/// This trait unifies both exclusive async locks (like `tokio::sync::Mutex`)
34/// and read-write async locks (like `tokio::sync::RwLock`) under a single
35/// interface. The key insight is that all async locks can be viewed as
36/// supporting two operations:
37///
38/// - **Read operations**: Provide immutable access (`&T`) to the data
39/// - **Write operations**: Provide mutable access (`&mut T`) to the data
40///
41/// For exclusive async locks (Mutex), both read and write operations
42/// acquire the same exclusive lock, but the API clearly indicates the
43/// intended usage. For read-write async locks (RwLock), read operations
44/// use shared locks while write operations use exclusive locks.
45///
46/// This design enables:
47/// - Unified API across different async lock types
48/// - Clear semantic distinction between read and write operations
49/// - Generic async code that works with any lock type
50/// - Performance optimization through appropriate lock selection
51/// - Non-blocking async operations
52///
53/// Only lock acquisition is asynchronous. The closure passed to `read` or
54/// `write` executes synchronously while the guard is held, so it cannot
55/// `.await` and should not perform blocking or long-running work. Compute
56/// expensive values before acquiring the lock, or move blocking work to a
57/// dedicated blocking task and keep the locked closure short.
58///
59/// This crate enables only Tokio's `sync` feature for its normal dependency.
60/// Applications that create a Tokio runtime as shown in the examples must
61/// enable an appropriate Tokio runtime feature such as `rt` or
62/// `rt-multi-thread`.
63///
64/// The trait intentionally returns `Send` futures. Closures and return values
65/// passed to `read` and `write` must be `Send`; Tokio mutex implementations
66/// require `T: Send`, and Tokio read-write lock implementations require
67/// `T: Send + Sync`.
68///
69/// # Performance Characteristics
70///
71/// Different async lock implementations have different performance
72/// characteristics:
73///
74/// ## Mutex-based async locks (ArcAsyncMutex, AsyncMutex)
75/// - `read`: Acquires exclusive lock, same performance as write
76/// - `write`: Acquires exclusive lock, same performance as read
77/// - **Use case**: When you need exclusive access or don't know access
78///   patterns
79///
80/// ## RwLock-based async locks (ArcAsyncRwLock, AsyncRwLock)
81/// - `read`: Acquires shared lock, allows concurrent readers
82/// - `write`: Acquires exclusive lock, blocks all other operations
83/// - **Use case**: Read-heavy async workloads where multiple readers can
84///   proceed concurrently
85///
86/// # Type Parameters
87///
88/// * `T` - The type of data protected by the lock
89///
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::{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::{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::{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::{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///
302impl<T: ?Sized + Send> AsyncLock<T> for AsyncMutex<T> {
303    /// Acquires the mutex and executes a read-only closure.
304    ///
305    /// # Arguments
306    ///
307    /// * `f` - Closure receiving immutable access to the protected value.
308    ///
309    /// # Returns
310    ///
311    /// A future resolving to the value returned by `f`.
312    #[inline]
313    async fn read<R, F>(&self, f: F) -> R
314    where
315        F: FnOnce(&T) -> R + Send,
316        R: Send,
317    {
318        let guard = self.lock().await;
319        f(&*guard)
320    }
321
322    /// Acquires the mutex and executes a mutable closure.
323    ///
324    /// # Arguments
325    ///
326    /// * `f` - Closure receiving mutable access to the protected value.
327    ///
328    /// # Returns
329    ///
330    /// A future resolving to the value returned by `f`.
331    #[inline]
332    async fn write<R, F>(&self, f: F) -> R
333    where
334        F: FnOnce(&mut T) -> R + Send,
335        R: Send,
336    {
337        let mut guard = self.lock().await;
338        f(&mut *guard)
339    }
340
341    /// Attempts to acquire the mutex without waiting for a read-only closure.
342    ///
343    /// # Arguments
344    ///
345    /// * `f` - Closure receiving immutable access when the mutex is acquired.
346    ///
347    /// # Returns
348    ///
349    /// `Ok(result)` if the mutex is acquired, or
350    /// [`TryLockError::WouldBlock`] if it is busy.
351    #[inline]
352    fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
353    where
354        F: FnOnce(&T) -> R,
355    {
356        self.try_lock()
357            .map(|guard| f(&*guard))
358            .map_err(|_| TryLockError::WouldBlock)
359    }
360
361    /// Attempts to acquire the mutex without waiting for a mutable closure.
362    ///
363    /// # Arguments
364    ///
365    /// * `f` - Closure receiving mutable access when the mutex is acquired.
366    ///
367    /// # Returns
368    ///
369    /// `Ok(result)` if the mutex is acquired, or
370    /// [`TryLockError::WouldBlock`] if it is busy.
371    #[inline]
372    fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
373    where
374        F: FnOnce(&mut T) -> R,
375    {
376        self.try_lock()
377            .map(|mut guard| f(&mut *guard))
378            .map_err(|_| TryLockError::WouldBlock)
379    }
380}
381
382/// Asynchronous read-write lock implementation for tokio::sync::RwLock
383///
384/// This implementation uses Tokio's `RwLock` type to provide an
385/// asynchronous read-write lock that supports multiple concurrent
386/// readers or a single writer without blocking threads. Read operations
387/// use shared locks allowing concurrent readers, while write operations
388/// use exclusive locks that block all other operations.
389///
390/// # Type Parameters
391///
392/// * `T` - The type of data protected by the lock
393///
394impl<T: ?Sized + Send + Sync> AsyncLock<T> for AsyncRwLock<T> {
395    /// Acquires a shared read lock and executes a closure.
396    ///
397    /// # Arguments
398    ///
399    /// * `f` - Closure receiving immutable access to the protected value.
400    ///
401    /// # Returns
402    ///
403    /// A future resolving to the value returned by `f`.
404    #[inline]
405    async fn read<R, F>(&self, f: F) -> R
406    where
407        F: FnOnce(&T) -> R + Send,
408        R: Send,
409    {
410        let guard = self.read().await;
411        f(&*guard)
412    }
413
414    /// Acquires an exclusive write lock and executes a closure.
415    ///
416    /// # Arguments
417    ///
418    /// * `f` - Closure receiving mutable access to the protected value.
419    ///
420    /// # Returns
421    ///
422    /// A future resolving to the value returned by `f`.
423    #[inline]
424    async fn write<R, F>(&self, f: F) -> R
425    where
426        F: FnOnce(&mut T) -> R + Send,
427        R: Send,
428    {
429        let mut guard = self.write().await;
430        f(&mut *guard)
431    }
432
433    /// Attempts to acquire a shared read lock without waiting.
434    ///
435    /// # Arguments
436    ///
437    /// * `f` - Closure receiving immutable access when the read lock is
438    ///   acquired.
439    ///
440    /// # Returns
441    ///
442    /// `Ok(result)` if a read lock is acquired, or
443    /// [`TryLockError::WouldBlock`] if it is busy.
444    #[inline]
445    fn try_read<R, F>(&self, f: F) -> Result<R, TryLockError>
446    where
447        F: FnOnce(&T) -> R,
448    {
449        self.try_read()
450            .map(|guard| f(&*guard))
451            .map_err(|_| TryLockError::WouldBlock)
452    }
453
454    /// Attempts to acquire an exclusive write lock without waiting.
455    ///
456    /// # Arguments
457    ///
458    /// * `f` - Closure receiving mutable access when the write lock is
459    ///   acquired.
460    ///
461    /// # Returns
462    ///
463    /// `Ok(result)` if a write lock is acquired, or
464    /// [`TryLockError::WouldBlock`] if it is busy.
465    #[inline]
466    fn try_write<R, F>(&self, f: F) -> Result<R, TryLockError>
467    where
468        F: FnOnce(&mut T) -> R,
469    {
470        self.try_write()
471            .map(|mut guard| f(&mut *guard))
472            .map_err(|_| TryLockError::WouldBlock)
473    }
474}