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