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 {}