Skip to main content

nestrs_locks/
lib.rs

1//! NestJS [`@nestjs/locks`](https://docs.nestjs.com/techniques/locks) analogue —
2//! lease-based distributed locks for single-runner cron and critical sections.
3//!
4//! # Example
5//!
6//! ```ignore
7//! use nestrs_locks::{LocksModule, LocksOptions, LocksService};
8//!
9//! let dm = LocksModule::register(LocksOptions::default());
10//! # async fn demo(locks: std::sync::Arc<LocksService>) {
11//! if let Some(guard) = locks.try_acquire("cron:nightly").await {
12//!     // exclusive work
13//!     guard.release().await;
14//! }
15//! # }
16//! ```
17//!
18//! Choose `default_ttl` carefully: shorter than tolerable duplicate work after a crash,
19//! longer than healthy job duration (or extend the lease). Redis store: feature `cache-redis`.
20//!
21//! Umbrella feature: `nestrs` / `locks` → `nestrs::locks`.
22
23#![doc(html_root_url = "https://docs.rs/nestrs-locks/1.7.1")]
24
25mod module;
26mod options;
27mod store;
28
29pub use module::LocksModule;
30pub use options::LocksOptions;
31pub use store::{
32    InMemoryLockStore, LockGuard, LockStore, SharedInMemoryLockStore, SharedLockStore,
33};
34
35#[cfg(feature = "cache-redis")]
36pub use store::RedisLockStore;
37
38pub use nestrs_core::{DynamicModule, Injectable, ProviderRegistry};
39use std::sync::Arc;
40use std::time::Duration;
41
42/// High-level lock service (NestJS LocksService analogue).
43pub struct LocksService {
44    store: SharedLockStore,
45    default_ttl: Duration,
46}
47
48impl LocksService {
49    pub fn from_options(opts: &LocksOptions) -> Self {
50        Self {
51            store: opts.store.clone(),
52            default_ttl: opts.default_ttl,
53        }
54    }
55
56    pub async fn try_acquire(&self, name: &str) -> Option<LockGuard> {
57        self.store.try_acquire(name, self.default_ttl).await
58    }
59
60    pub async fn with_lock<F, Fut, T>(&self, name: &str, f: F) -> Option<T>
61    where
62        F: FnOnce() -> Fut,
63        Fut: std::future::Future<Output = T>,
64    {
65        let guard = self.try_acquire(name).await?;
66        let out = f().await;
67        guard.release().await;
68        Some(out)
69    }
70}
71
72impl Injectable for LocksService {
73    fn construct(_registry: &nestrs_core::ProviderRegistry) -> Arc<Self> {
74        panic!("LocksService must be provided by LocksModule::register(...)");
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[tokio::test]
83    async fn exclusive_lock() {
84        let svc = LocksService::from_options(&LocksOptions::default());
85        let g1 = svc.try_acquire("cron").await.expect("first");
86        assert!(svc.try_acquire("cron").await.is_none());
87        g1.release().await;
88        assert!(svc.try_acquire("cron").await.is_some());
89    }
90}