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}