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}