distributed_lock_core/traits.rs
1//! Core traits for distributed locks.
2
3use std::future::Future;
4use std::time::Duration;
5
6use crate::error::LockResult;
7
8// ============================================================================
9// Lock Handle Trait
10// ============================================================================
11
12/// Handle to a held distributed lock.
13///
14/// Dropping this handle releases the lock. For proper error handling in async
15/// contexts, call `release()` explicitly.
16///
17/// # Example
18///
19/// ```rust,ignore
20/// let handle = lock.acquire(None).await?;
21/// // Critical section - we hold the lock
22/// do_work().await;
23/// // Explicit release with error handling
24/// handle.release().await?;
25/// ```
26pub trait LockHandle: Send + Sync + Sized {
27 /// Returns a receiver that signals when the lock is lost.
28 ///
29 /// The receiver yields `true` when the lock is lost (e.g., connection died).
30 /// Not all backends support this; unsupported backends return a receiver
31 /// that never changes from `false`.
32 ///
33 /// # Example
34 ///
35 /// ```rust,ignore
36 /// tokio::select! {
37 /// _ = handle.lost_token().changed() => {
38 /// eprintln!("Lock was lost!");
39 /// }
40 /// _ = do_work() => {
41 /// // Work completed while still holding lock
42 /// }
43 /// }
44 /// ```
45 fn lost_token(&self) -> &tokio::sync::watch::Receiver<bool>;
46
47 /// Explicitly releases the lock.
48 ///
49 /// This is also called automatically on drop, but the async version
50 /// allows proper error handling.
51 fn release(self) -> impl Future<Output = LockResult<()>> + Send;
52}
53
54// ============================================================================
55// Distributed Lock Trait
56// ============================================================================
57
58/// A distributed mutual exclusion lock.
59///
60/// Provides exclusive access to a resource identified by `name` across
61/// processes and machines. The specific backend (PostgreSQL, Redis, file
62/// system, etc.) determines how the lock is implemented.
63///
64/// # Example
65///
66/// ```rust,ignore
67/// use distributed_lock_core::DistributedLock;
68///
69/// async fn protected_operation(lock: &impl DistributedLock) -> Result<(), Error> {
70/// // Acquire with 5 second timeout
71/// let handle = lock.acquire(Some(Duration::from_secs(5))).await?;
72///
73/// // We have exclusive access
74/// perform_critical_section().await?;
75///
76/// // Release (also happens on drop)
77/// handle.release().await?;
78/// Ok(())
79/// }
80/// ```
81pub trait DistributedLock: Send + Sync {
82 /// The handle type returned when the lock is acquired.
83 type Handle: LockHandle + Send;
84
85 /// Returns the unique name identifying this lock.
86 fn name(&self) -> &str;
87
88 /// Acquires the lock, waiting up to `timeout`.
89 ///
90 /// # Arguments
91 ///
92 /// * `timeout` - Maximum time to wait. `None` means wait indefinitely.
93 ///
94 /// # Returns
95 ///
96 /// * `Ok(handle)` - Lock acquired successfully
97 /// * `Err(LockError::Timeout)` - Timeout expired before lock acquired
98 /// * `Err(LockError::Cancelled)` - Operation was cancelled
99 /// * `Err(LockError::Connection)` - Backend connection failed
100 ///
101 /// # Cancellation
102 ///
103 /// This operation can be cancelled by dropping the returned future or using
104 /// `tokio::select!` with a cancellation branch. Backends should check for
105 /// cancellation periodically during the wait.
106 fn acquire(
107 &self,
108 timeout: Option<Duration>,
109 ) -> impl Future<Output = LockResult<Self::Handle>> + Send;
110
111 /// Attempts to acquire the lock without waiting.
112 ///
113 /// # Returns
114 ///
115 /// * `Ok(Some(handle))` - Lock acquired successfully
116 /// * `Ok(None)` - Lock is held by another process
117 /// * `Err(...)` - Error occurred during attempt
118 fn try_acquire(&self) -> impl Future<Output = LockResult<Option<Self::Handle>>> + Send;
119}
120
121// ============================================================================
122// Reader-Writer Lock Trait
123// ============================================================================
124
125/// A distributed reader-writer lock.
126///
127/// Allows multiple concurrent readers OR a single exclusive writer.
128/// Writers are given priority to prevent starvation.
129///
130/// # Example
131///
132/// ```rust,ignore
133/// // Multiple readers can hold the lock simultaneously
134/// let read_handle = lock.acquire_read(None).await?;
135/// let data = read_shared_resource().await;
136/// read_handle.release().await?;
137///
138/// // Writers get exclusive access
139/// let write_handle = lock.acquire_write(None).await?;
140/// modify_shared_resource().await;
141/// write_handle.release().await?;
142/// ```
143pub trait DistributedReaderWriterLock: Send + Sync {
144 /// Handle type for read (shared) locks.
145 type ReadHandle: LockHandle + Send;
146 /// Handle type for write (exclusive) locks.
147 type WriteHandle: LockHandle + Send;
148
149 /// Returns the unique name identifying this lock.
150 fn name(&self) -> &str;
151
152 /// Acquires a read (shared) lock.
153 ///
154 /// Multiple readers can hold the lock concurrently.
155 /// Blocks if a writer holds or is waiting for the lock.
156 fn acquire_read(
157 &self,
158 timeout: Option<Duration>,
159 ) -> impl Future<Output = LockResult<Self::ReadHandle>> + Send;
160
161 /// Attempts to acquire a read lock without waiting.
162 fn try_acquire_read(&self)
163 -> impl Future<Output = LockResult<Option<Self::ReadHandle>>> + Send;
164
165 /// Acquires a write (exclusive) lock.
166 ///
167 /// Only one writer can hold the lock. Blocks all readers.
168 fn acquire_write(
169 &self,
170 timeout: Option<Duration>,
171 ) -> impl Future<Output = LockResult<Self::WriteHandle>> + Send;
172
173 /// Attempts to acquire a write lock without waiting.
174 fn try_acquire_write(
175 &self,
176 ) -> impl Future<Output = LockResult<Option<Self::WriteHandle>>> + Send;
177}
178
179// ============================================================================
180// Semaphore Trait
181// ============================================================================
182
183/// A distributed counting semaphore.
184///
185/// Allows up to `max_count` processes to hold the semaphore concurrently.
186/// Useful for rate limiting or resource pooling.
187///
188/// # Example
189///
190/// ```rust,ignore
191/// // Create a semaphore allowing 5 concurrent database connections
192/// let semaphore = provider.create_semaphore("db-pool", 5);
193///
194/// // Acquire a "ticket"
195/// let ticket = semaphore.acquire(None).await?;
196///
197/// // Use the limited resource
198/// use_database_connection().await;
199///
200/// // Release the ticket
201/// ticket.release().await?;
202/// ```
203pub trait DistributedSemaphore: Send + Sync {
204 /// Handle type for semaphore tickets.
205 type Handle: LockHandle + Send;
206
207 /// Returns the unique name identifying this semaphore.
208 fn name(&self) -> &str;
209
210 /// Returns the maximum number of concurrent holders.
211 fn max_count(&self) -> u32;
212
213 /// Acquires a semaphore ticket.
214 ///
215 /// Blocks if `max_count` tickets are already held.
216 fn acquire(
217 &self,
218 timeout: Option<Duration>,
219 ) -> impl Future<Output = LockResult<Self::Handle>> + Send;
220
221 /// Attempts to acquire a ticket without waiting.
222 fn try_acquire(&self) -> impl Future<Output = LockResult<Option<Self::Handle>>> + Send;
223}
224
225// ============================================================================
226// Provider Traits
227// ============================================================================
228
229/// Factory for creating distributed locks by name.
230///
231/// Providers encapsulate backend configuration, allowing application code
232/// to be backend-agnostic.
233///
234/// # Example
235///
236/// ```rust,ignore
237/// // Configure once at startup
238/// let provider = PostgresLockProvider::new(connection_string);
239///
240/// // Create locks by name anywhere in the application
241/// let lock = provider.create_lock("my-resource");
242/// let handle = lock.acquire(None).await?;
243/// ```
244pub trait LockProvider: Send + Sync {
245 /// The lock type created by this provider.
246 type Lock: DistributedLock;
247
248 /// Creates a lock with the given name.
249 fn create_lock(&self, name: &str) -> Self::Lock;
250}
251
252/// Factory for creating reader-writer locks by name.
253pub trait ReaderWriterLockProvider: Send + Sync {
254 /// The lock type created by this provider.
255 type Lock: DistributedReaderWriterLock;
256
257 /// Creates a reader-writer lock with the given name.
258 fn create_reader_writer_lock(&self, name: &str) -> Self::Lock;
259}
260
261/// Factory for creating semaphores by name.
262pub trait SemaphoreProvider: Send + Sync {
263 /// The semaphore type created by this provider.
264 type Semaphore: DistributedSemaphore;
265
266 /// Creates a semaphore with the given name and max count.
267 fn create_semaphore(&self, name: &str, max_count: u32) -> Self::Semaphore;
268}
269
270// ============================================================================
271// Convenience Extensions
272// ============================================================================
273
274/// Extension trait providing convenience methods for lock providers.
275pub trait LockProviderExt: LockProvider {
276 /// Acquires a lock by name, returning the handle.
277 ///
278 /// Convenience method combining `create_lock` and `acquire`.
279 fn acquire_lock(
280 &self,
281 name: &str,
282 timeout: Option<Duration>,
283 ) -> impl Future<Output = LockResult<<Self::Lock as DistributedLock>::Handle>> + Send
284 where
285 Self: Sync,
286 {
287 async move {
288 let lock = self.create_lock(name);
289 lock.acquire(timeout).await
290 }
291 }
292
293 /// Tries to acquire a lock by name.
294 ///
295 /// Convenience method combining `create_lock` and `try_acquire`.
296 fn try_acquire_lock(
297 &self,
298 name: &str,
299 ) -> impl Future<Output = LockResult<Option<<Self::Lock as DistributedLock>::Handle>>> + Send
300 where
301 Self: Sync,
302 {
303 async move {
304 let lock = self.create_lock(name);
305 lock.try_acquire().await
306 }
307 }
308}
309
310// Blanket implementation for all LockProviders
311impl<T: LockProvider> LockProviderExt for T {}