proxima_primitives/sync/mutex.rs
1//! `proxima::sync::Mutex` — async mutex, shape-compatible with
2//! `tokio::sync::Mutex`. Backed by `futures::lock::Mutex` (no runtime
3//! coupling). Thin newtype: forwards `lock` / `get_mut` / `into_inner`
4//! to the futures variant; reshapes `try_lock` so the return is
5//! `Result<MutexGuard<'_, T>, TryLockError>` matching tokio's shape
6//! rather than the futures crate's `Option<MutexGuard<'_, T>>`.
7//!
8//! Cancel-safety nuance: tokio's `lock()` future is documented
9//! cancel-safe — dropping the future before it resolves leaves the
10//! mutex untouched. `futures::lock::Mutex::lock()` has the same
11//! property (the future never holds the lock until it resolves), so
12//! the semantics carry over.
13//!
14//! # Non-coverage
15//!
16//! The following tokio APIs are NOT shimmed:
17//!
18//! - `lock_owned() -> OwnedMutexGuard<T>` — tokio's owned-guard
19//! variant requires `Arc<Mutex<T>>` self and returns a guard whose
20//! lifetime is decoupled from any reference. `futures::lock::Mutex`
21//! has no analog. Migration: clone the `Arc<Mutex<T>>`, hold it
22//! alongside the guard, drop guard first.
23//! - `try_lock_owned() -> Result<OwnedMutexGuard<T>, TryLockError>` —
24//! same as above; no owned variant.
25//! - `Mutex::blocking_lock()` — sync-context lock acquisition. Don't
26//! call sync APIs from async code; if you need this, the design is
27//! probably wrong. Use `std::sync::Mutex` for non-async paths.
28
29use core::fmt;
30
31pub use futures::lock::{MutexGuard, MutexLockFuture};
32
33/// Async mutex matching `tokio::sync::Mutex`'s API shape, backed by
34/// `futures::lock::Mutex`.
35#[derive(Debug, Default)]
36pub struct Mutex<T: ?Sized>(futures::lock::Mutex<T>);
37
38impl<T> Mutex<T> {
39 /// Construct a new mutex around `value`.
40 #[must_use]
41 pub fn new(value: T) -> Self {
42 Self(futures::lock::Mutex::new(value))
43 }
44
45 /// Unwrap the mutex, returning the inner value.
46 pub fn into_inner(self) -> T {
47 self.0.into_inner()
48 }
49}
50
51impl<T: ?Sized> Mutex<T> {
52 /// Acquire the mutex asynchronously.
53 pub fn lock(&self) -> MutexLockFuture<'_, T> {
54 self.0.lock()
55 }
56
57 /// Try to acquire without waiting. Matches tokio's `Result` shape
58 /// so callers can `?` the error or `match` on success without
59 /// adapting the futures-crate `Option` form.
60 pub fn try_lock(&self) -> Result<MutexGuard<'_, T>, TryLockError> {
61 self.0.try_lock().ok_or(TryLockError(()))
62 }
63
64 /// Mutable access without locking — `&mut self` proves exclusivity
65 /// statically.
66 pub fn get_mut(&mut self) -> &mut T {
67 self.0.get_mut()
68 }
69}
70
71/// Error returned by `Mutex::try_lock` when the lock would block.
72/// Matches `tokio::sync::TryLockError`'s shape (unit-payload).
73#[derive(Debug)]
74pub struct TryLockError(());
75
76/// Crate-internal constructor — other primitives (`RwLock`) that
77/// also reshape `Option` → `Result<_, TryLockError>` need to build
78/// the error without exposing the private inner.
79pub(crate) fn try_lock_error() -> TryLockError {
80 TryLockError(())
81}
82
83impl fmt::Display for TryLockError {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 formatter.write_str("operation would block")
86 }
87}
88
89impl std::error::Error for TryLockError {}