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}