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}