1use crate::config::ScheduleError;
7use chrono::{DateTime, Duration, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ScheduleLock {
14 pub task_name: String,
16 pub owner: String,
18 pub acquired_at: DateTime<Utc>,
20 pub expires_at: DateTime<Utc>,
22 pub renewal_count: u32,
24}
25
26impl ScheduleLock {
27 pub fn new(task_name: String, owner: String, ttl_seconds: u64) -> Self {
34 let now = Utc::now();
35 Self {
36 task_name,
37 owner,
38 acquired_at: now,
39 expires_at: now + Duration::seconds(ttl_seconds as i64),
40 renewal_count: 0,
41 }
42 }
43
44 pub fn is_expired(&self) -> bool {
46 Utc::now() > self.expires_at
47 }
48
49 pub fn is_owned_by(&self, owner: &str) -> bool {
51 self.owner == owner
52 }
53
54 pub fn renew(&mut self, ttl_seconds: u64) -> Result<(), ScheduleError> {
63 if self.is_expired() {
64 return Err(ScheduleError::Invalid(
65 "Cannot renew expired lock".to_string(),
66 ));
67 }
68
69 self.expires_at = Utc::now() + Duration::seconds(ttl_seconds as i64);
70 self.renewal_count += 1;
71 Ok(())
72 }
73
74 pub fn ttl(&self) -> Duration {
76 self.expires_at - Utc::now()
77 }
78
79 pub fn age(&self) -> Duration {
81 Utc::now() - self.acquired_at
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct LockManager {
88 pub(crate) locks: HashMap<String, ScheduleLock>,
90 pub(crate) default_ttl: u64,
92}
93
94impl LockManager {
95 pub fn new(default_ttl: u64) -> Self {
100 Self {
101 locks: HashMap::new(),
102 default_ttl,
103 }
104 }
105
106 pub fn try_acquire(
118 &mut self,
119 task_name: &str,
120 owner: &str,
121 ttl: Option<u64>,
122 ) -> Result<bool, ScheduleError> {
123 self.cleanup_expired();
125
126 if let Some(existing_lock) = self.locks.get(task_name) {
128 if !existing_lock.is_expired() {
129 if !existing_lock.is_owned_by(owner) {
131 return Ok(false);
132 }
133 return Ok(true);
135 }
136 }
137
138 let ttl_seconds = ttl.unwrap_or(self.default_ttl);
140 let lock = ScheduleLock::new(task_name.to_string(), owner.to_string(), ttl_seconds);
141 self.locks.insert(task_name.to_string(), lock);
142 Ok(true)
143 }
144
145 pub fn release(&mut self, task_name: &str, owner: &str) -> Result<bool, ScheduleError> {
155 if let Some(lock) = self.locks.get(task_name) {
156 if lock.is_owned_by(owner) {
157 self.locks.remove(task_name);
158 return Ok(true);
159 }
160 }
161 Ok(false)
162 }
163
164 pub fn renew(
175 &mut self,
176 task_name: &str,
177 owner: &str,
178 ttl: Option<u64>,
179 ) -> Result<bool, ScheduleError> {
180 if let Some(lock) = self.locks.get_mut(task_name) {
181 if lock.is_owned_by(owner) && !lock.is_expired() {
182 let ttl_seconds = ttl.unwrap_or(self.default_ttl);
183 lock.renew(ttl_seconds)?;
184 return Ok(true);
185 }
186 }
187 Ok(false)
188 }
189
190 pub fn is_locked(&self, task_name: &str) -> bool {
199 if let Some(lock) = self.locks.get(task_name) {
200 !lock.is_expired()
201 } else {
202 false
203 }
204 }
205
206 pub fn get_lock(&self, task_name: &str) -> Option<&ScheduleLock> {
208 self.locks.get(task_name)
209 }
210
211 pub fn cleanup_expired(&mut self) {
213 self.locks.retain(|_, lock| !lock.is_expired());
214 }
215
216 pub fn get_active_locks(&self) -> Vec<&ScheduleLock> {
218 self.locks
219 .values()
220 .filter(|lock| !lock.is_expired())
221 .collect()
222 }
223
224 pub fn release_all(&mut self) {
226 self.locks.clear();
227 }
228}
229
230impl Default for LockManager {
231 fn default() -> Self {
232 Self::new(300) }
234}
235
236use async_trait::async_trait;
241use celers_core::lock::DistributedLockBackend;
242use std::sync::Arc;
243use std::time::{Duration as StdDuration, Instant};
244use tokio::sync::RwLock;
245
246#[derive(Debug, Clone)]
248struct LockEntry {
249 owner: String,
251 acquired_at: Instant,
253 ttl: StdDuration,
255}
256
257impl LockEntry {
258 fn is_expired(&self) -> bool {
260 self.acquired_at.elapsed() >= self.ttl
261 }
262}
263
264#[derive(Debug, Clone)]
286pub struct InMemoryLockBackend {
287 locks: Arc<RwLock<HashMap<String, LockEntry>>>,
288}
289
290impl InMemoryLockBackend {
291 pub fn new() -> Self {
293 Self {
294 locks: Arc::new(RwLock::new(HashMap::new())),
295 }
296 }
297
298 pub async fn active_lock_count(&self) -> usize {
300 let locks = self.locks.read().await;
301 locks.values().filter(|e| !e.is_expired()).count()
302 }
303}
304
305impl Default for InMemoryLockBackend {
306 fn default() -> Self {
307 Self::new()
308 }
309}
310
311#[async_trait]
312impl DistributedLockBackend for InMemoryLockBackend {
313 async fn try_acquire(
314 &self,
315 key: &str,
316 owner: &str,
317 ttl_secs: u64,
318 ) -> celers_core::error::Result<bool> {
319 let mut locks = self.locks.write().await;
320
321 locks.retain(|_, entry| !entry.is_expired());
323
324 if let Some(existing) = locks.get(key) {
326 if !existing.is_expired() {
327 if existing.owner == owner {
329 return Ok(true);
330 }
331 return Ok(false);
333 }
334 }
335
336 locks.insert(
338 key.to_string(),
339 LockEntry {
340 owner: owner.to_string(),
341 acquired_at: Instant::now(),
342 ttl: StdDuration::from_secs(ttl_secs),
343 },
344 );
345 Ok(true)
346 }
347
348 async fn release(&self, key: &str, owner: &str) -> celers_core::error::Result<bool> {
349 let mut locks = self.locks.write().await;
350
351 if let Some(entry) = locks.get(key) {
352 if entry.owner == owner {
353 locks.remove(key);
354 return Ok(true);
355 }
356 }
357 Ok(false)
358 }
359
360 async fn renew(
361 &self,
362 key: &str,
363 owner: &str,
364 ttl_secs: u64,
365 ) -> celers_core::error::Result<bool> {
366 let mut locks = self.locks.write().await;
367
368 if let Some(entry) = locks.get_mut(key) {
369 if entry.owner == owner && !entry.is_expired() {
370 entry.acquired_at = Instant::now();
371 entry.ttl = StdDuration::from_secs(ttl_secs);
372 return Ok(true);
373 }
374 }
375 Ok(false)
376 }
377
378 async fn is_locked(&self, key: &str) -> celers_core::error::Result<bool> {
379 let locks = self.locks.read().await;
380 if let Some(entry) = locks.get(key) {
381 Ok(!entry.is_expired())
382 } else {
383 Ok(false)
384 }
385 }
386
387 async fn owner(&self, key: &str) -> celers_core::error::Result<Option<String>> {
388 let locks = self.locks.read().await;
389 if let Some(entry) = locks.get(key) {
390 if !entry.is_expired() {
391 return Ok(Some(entry.owner.clone()));
392 }
393 }
394 Ok(None)
395 }
396
397 async fn release_all(&self, owner: &str) -> celers_core::error::Result<u64> {
398 let mut locks = self.locks.write().await;
399 let before = locks.len();
400 locks.retain(|_, entry| entry.owner != owner);
401 let after = locks.len();
402 Ok((before - after) as u64)
403 }
404}
405
406#[cfg(test)]
407mod in_memory_lock_tests {
408 use super::*;
409
410 #[tokio::test]
411 async fn test_acquire_and_release() {
412 let backend = InMemoryLockBackend::new();
413
414 let acquired = backend.try_acquire("task1", "owner1", 300).await;
416 assert!(acquired.is_ok());
417 assert!(acquired.as_ref().is_ok_and(|v| *v));
418
419 let locked = backend.is_locked("task1").await;
421 assert!(locked.is_ok_and(|v| v));
422
423 let owner = backend.owner("task1").await;
425 assert!(owner.is_ok());
426 assert_eq!(
427 owner.as_ref().ok().and_then(|o| o.as_deref()),
428 Some("owner1")
429 );
430
431 let released = backend.release("task1", "owner1").await;
433 assert!(released.is_ok_and(|v| v));
434
435 let locked = backend.is_locked("task1").await;
437 assert!(locked.is_ok_and(|v| !v));
438 }
439
440 #[tokio::test]
441 async fn test_acquire_fails_for_different_owner() {
442 let backend = InMemoryLockBackend::new();
443
444 let acquired = backend.try_acquire("task1", "owner1", 300).await;
445 assert!(acquired.is_ok_and(|v| v));
446
447 let acquired2 = backend.try_acquire("task1", "owner2", 300).await;
449 assert!(acquired2.is_ok_and(|v| !v));
450 }
451
452 #[tokio::test]
453 async fn test_same_owner_can_reacquire() {
454 let backend = InMemoryLockBackend::new();
455
456 let acquired = backend.try_acquire("task1", "owner1", 300).await;
457 assert!(acquired.is_ok_and(|v| v));
458
459 let acquired2 = backend.try_acquire("task1", "owner1", 300).await;
461 assert!(acquired2.is_ok_and(|v| v));
462 }
463
464 #[tokio::test]
465 async fn test_release_wrong_owner_fails() {
466 let backend = InMemoryLockBackend::new();
467
468 let _ = backend.try_acquire("task1", "owner1", 300).await;
469
470 let released = backend.release("task1", "owner2").await;
472 assert!(released.is_ok_and(|v| !v));
473
474 let locked = backend.is_locked("task1").await;
476 assert!(locked.is_ok_and(|v| v));
477 }
478
479 #[tokio::test]
480 async fn test_renew() {
481 let backend = InMemoryLockBackend::new();
482
483 let _ = backend.try_acquire("task1", "owner1", 300).await;
484
485 let renewed = backend.renew("task1", "owner1", 600).await;
487 assert!(renewed.is_ok_and(|v| v));
488
489 let renewed2 = backend.renew("task1", "owner2", 600).await;
491 assert!(renewed2.is_ok_and(|v| !v));
492 }
493
494 #[tokio::test]
495 async fn test_renew_nonexistent_fails() {
496 let backend = InMemoryLockBackend::new();
497
498 let renewed = backend.renew("nonexistent", "owner1", 600).await;
499 assert!(renewed.is_ok_and(|v| !v));
500 }
501
502 #[tokio::test]
503 async fn test_expiry() {
504 let backend = InMemoryLockBackend::new();
505
506 let _ = backend.try_acquire("task1", "owner1", 0).await;
509
510 let locked = backend.is_locked("task1").await;
512 assert!(locked.is_ok_and(|v| !v));
513
514 let acquired = backend.try_acquire("task1", "owner2", 300).await;
516 assert!(acquired.is_ok_and(|v| v));
517 }
518
519 #[tokio::test]
520 async fn test_release_all() {
521 let backend = InMemoryLockBackend::new();
522
523 let _ = backend.try_acquire("task1", "owner1", 300).await;
524 let _ = backend.try_acquire("task2", "owner1", 300).await;
525 let _ = backend.try_acquire("task3", "owner2", 300).await;
526
527 let count = backend.release_all("owner1").await;
529 assert!(count.is_ok());
530 assert_eq!(count.ok(), Some(2));
531
532 let locked = backend.is_locked("task3").await;
534 assert!(locked.is_ok_and(|v| v));
535
536 let locked1 = backend.is_locked("task1").await;
538 assert!(locked1.is_ok_and(|v| !v));
539 }
540
541 #[tokio::test]
542 async fn test_owner_of_nonexistent() {
543 let backend = InMemoryLockBackend::new();
544
545 let owner = backend.owner("nonexistent").await;
546 assert!(owner.is_ok());
547 assert_eq!(owner.ok().flatten(), None);
548 }
549
550 #[tokio::test]
551 async fn test_release_nonexistent() {
552 let backend = InMemoryLockBackend::new();
553
554 let released = backend.release("nonexistent", "owner1").await;
555 assert!(released.is_ok_and(|v| !v));
556 }
557
558 #[tokio::test]
559 async fn test_active_lock_count() {
560 let backend = InMemoryLockBackend::new();
561
562 let _ = backend.try_acquire("task1", "owner1", 300).await;
563 let _ = backend.try_acquire("task2", "owner1", 300).await;
564
565 assert_eq!(backend.active_lock_count().await, 2);
566
567 let _ = backend.release("task1", "owner1").await;
568 assert_eq!(backend.active_lock_count().await, 1);
569 }
570
571 #[tokio::test]
572 async fn test_multiple_keys_independent() {
573 let backend = InMemoryLockBackend::new();
574
575 let _ = backend.try_acquire("task_a", "owner1", 300).await;
576 let _ = backend.try_acquire("task_b", "owner2", 300).await;
577
578 assert!(backend.is_locked("task_a").await.is_ok_and(|v| v));
580 assert!(backend.is_locked("task_b").await.is_ok_and(|v| v));
581
582 let owner_a = backend.owner("task_a").await;
584 let owner_b = backend.owner("task_b").await;
585 assert_eq!(owner_a.ok().flatten().as_deref(), Some("owner1"));
586 assert_eq!(owner_b.ok().flatten().as_deref(), Some("owner2"));
587 }
588}