Skip to main content

celers_core/
lock.rs

1//! Distributed lock backend trait for beat scheduler
2//!
3//! Provides the `DistributedLockBackend` trait that enables distributed lock
4//! management across multiple beat scheduler instances. Implementations can
5//! use Redis, databases, or in-memory stores as the backing mechanism.
6
7use async_trait::async_trait;
8
9/// Backend for distributed lock management.
10///
11/// Used by the beat scheduler to prevent duplicate task execution across
12/// multiple scheduler instances. Each implementation provides atomic
13/// lock operations with TTL-based expiration.
14///
15/// # Lock Semantics
16///
17/// - Locks are identified by a string key (typically the task name)
18/// - Each lock has an owner (typically the scheduler instance ID)
19/// - Locks expire after a configurable TTL to prevent deadlocks
20/// - Only the owner can release or renew a lock
21///
22/// # Example
23///
24/// ```ignore
25/// use celers_core::lock::DistributedLockBackend;
26///
27/// async fn schedule_task(backend: &dyn DistributedLockBackend) {
28///     let acquired = backend.try_acquire("my_task", "scheduler-1", 300).await.unwrap();
29///     if acquired {
30///         // Execute the task
31///         // ...
32///         backend.release("my_task", "scheduler-1").await.unwrap();
33///     }
34/// }
35/// ```
36#[async_trait]
37pub trait DistributedLockBackend: Send + Sync + std::fmt::Debug {
38    /// Try to acquire a lock. Returns `Ok(true)` if acquired successfully.
39    ///
40    /// If the lock is already held by the same owner, this should return `Ok(true)`.
41    /// If the lock is held by a different owner and has not expired, returns `Ok(false)`.
42    /// If the lock has expired, it may be acquired by the new owner.
43    ///
44    /// # Arguments
45    /// * `key` - Lock identifier (typically the task name)
46    /// * `owner` - Owner identifier (typically the scheduler instance ID)
47    /// * `ttl_secs` - Time-to-live for the lock in seconds
48    async fn try_acquire(
49        &self,
50        key: &str,
51        owner: &str,
52        ttl_secs: u64,
53    ) -> crate::error::Result<bool>;
54
55    /// Release a lock. Returns `Ok(true)` if released (was owned by this owner).
56    ///
57    /// Only the current owner can release a lock. Returns `Ok(false)` if the
58    /// lock does not exist or is owned by a different owner.
59    ///
60    /// # Arguments
61    /// * `key` - Lock identifier
62    /// * `owner` - Owner identifier (must match the lock's owner)
63    async fn release(&self, key: &str, owner: &str) -> crate::error::Result<bool>;
64
65    /// Renew/extend a lock's TTL. Returns `Ok(true)` if renewed.
66    ///
67    /// Only the current owner can renew a lock. The lock must not have expired.
68    /// Returns `Ok(false)` if the lock does not exist, is owned by a different
69    /// owner, or has already expired.
70    ///
71    /// # Arguments
72    /// * `key` - Lock identifier
73    /// * `owner` - Owner identifier (must match the lock's owner)
74    /// * `ttl_secs` - New time-to-live for the lock in seconds
75    async fn renew(&self, key: &str, owner: &str, ttl_secs: u64) -> crate::error::Result<bool>;
76
77    /// Check if a lock is currently held by anyone.
78    ///
79    /// Returns `Ok(true)` if the lock exists and has not expired.
80    async fn is_locked(&self, key: &str) -> crate::error::Result<bool>;
81
82    /// Get the current owner of a lock, if any.
83    ///
84    /// Returns `Ok(None)` if the lock does not exist or has expired.
85    async fn owner(&self, key: &str) -> crate::error::Result<Option<String>>;
86
87    /// Release all locks owned by a specific owner.
88    ///
89    /// Returns the number of locks that were released. This is useful for
90    /// cleanup when a scheduler instance shuts down.
91    async fn release_all(&self, owner: &str) -> crate::error::Result<u64>;
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    /// Helper to verify that trait objects can be created
99    #[test]
100    fn test_trait_is_object_safe() {
101        fn _assert_object_safe(_: &dyn DistributedLockBackend) {}
102    }
103}