1#![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
42pub 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}