distributed_lock_redis/
provider.rs

1//! Redis lock provider implementation.
2
3use std::time::Duration;
4
5use distributed_lock_core::error::{LockError, LockResult};
6use distributed_lock_core::traits::{LockProvider, ReaderWriterLockProvider, SemaphoreProvider};
7
8use crate::lock::RedisDistributedLock;
9use crate::rw_lock::RedisDistributedReaderWriterLock;
10use crate::semaphore::RedisDistributedSemaphore;
11use fred::prelude::*;
12
13/// Builder for Redis lock provider configuration.
14pub struct RedisLockProviderBuilder {
15    urls: Vec<String>,
16    clients: Vec<RedisClient>,
17    expiry: Duration,
18    extension_cadence: Duration,
19    min_validity: Duration,
20}
21
22impl RedisLockProviderBuilder {
23    /// Creates a new builder with default settings.
24    pub fn new() -> Self {
25        Self {
26            urls: vec![],
27            clients: vec![],
28            expiry: Duration::from_secs(30),
29            extension_cadence: Duration::from_secs(10),
30            min_validity: Duration::from_millis(27000), // 90% of expiry
31        }
32    }
33
34    /// Adds a Redis server URL.
35    ///
36    /// For RedLock, add multiple URLs (ideally 3 or 5).
37    pub fn url(mut self, url: impl Into<String>) -> Self {
38        self.urls.push(url.into());
39        self
40    }
41
42    /// Adds multiple Redis server URLs.
43    pub fn urls(mut self, urls: &[impl AsRef<str>]) -> Self {
44        for url in urls {
45            self.urls.push(url.as_ref().to_string());
46        }
47        self
48    }
49
50    /// Uses an existing Redis client.
51    pub fn client(mut self, client: RedisClient) -> Self {
52        self.clients.push(client);
53        self
54    }
55
56    /// Sets the lock expiry time.
57    pub fn expiry(mut self, expiry: Duration) -> Self {
58        self.expiry = expiry;
59        self
60    }
61
62    /// Sets the lock extension cadence.
63    pub fn extension_cadence(mut self, cadence: Duration) -> Self {
64        self.extension_cadence = cadence;
65        self
66    }
67
68    /// Sets the minimum validity time.
69    ///
70    /// After acquiring, at least this much time must remain on the lock
71    /// for the acquisition to be considered successful.
72    pub fn min_validity(mut self, validity: Duration) -> Self {
73        self.min_validity = validity;
74        self
75    }
76
77    /// Builds the provider.
78    pub async fn build(self) -> LockResult<RedisLockProvider> {
79        let mut clients = self.clients;
80
81        // Create clients from URLs if provided
82        for url in self.urls {
83            let config = RedisConfig::from_url(&url).map_err(|e| {
84                LockError::Connection(Box::new(std::io::Error::new(
85                    std::io::ErrorKind::InvalidInput,
86                    format!("invalid Redis URL: {}", e),
87                )))
88            })?;
89
90            let client = RedisClient::new(config, None, None, None);
91            client.connect();
92            client.wait_for_connect().await.map_err(|e| {
93                LockError::Connection(Box::new(std::io::Error::other(format!(
94                    "failed to connect to Redis: {}",
95                    e
96                ))))
97            })?;
98
99            clients.push(client);
100        }
101
102        if clients.is_empty() {
103            return Err(LockError::InvalidName(
104                "no Redis clients or URLs provided".to_string(),
105            ));
106        }
107
108        Ok(RedisLockProvider {
109            clients: clients.clone(),
110            primary_client: clients.into_iter().next().unwrap(), // For semaphores, use single client
111            expiry: self.expiry,
112            extension_cadence: self.extension_cadence,
113            min_validity: self.min_validity,
114        })
115    }
116}
117
118impl Default for RedisLockProviderBuilder {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124/// Provider for Redis-based distributed locks.
125///
126/// Supports single-server and multi-server (RedLock) configurations.
127pub struct RedisLockProvider {
128    /// All Redis clients (for RedLock).
129    clients: Vec<RedisClient>,
130    /// Primary client (for semaphores which don't support RedLock).
131    primary_client: RedisClient,
132    /// Lock expiry time.
133    expiry: Duration,
134    /// Extension cadence.
135    extension_cadence: Duration,
136    /// Minimum validity time.
137    min_validity: Duration,
138}
139
140impl RedisLockProvider {
141    /// Returns a new builder for configuring the provider.
142    pub fn builder() -> RedisLockProviderBuilder {
143        RedisLockProviderBuilder::new()
144    }
145
146    /// Creates a provider using the specified Redis URL.
147    pub async fn new(url: impl Into<String>) -> LockResult<Self> {
148        Self::builder().url(url).build().await
149    }
150}
151
152impl LockProvider for RedisLockProvider {
153    type Lock = RedisDistributedLock;
154
155    fn create_lock(&self, name: &str) -> Self::Lock {
156        RedisDistributedLock::new(
157            name.to_string(),
158            self.clients.clone(),
159            self.expiry,
160            self.min_validity,
161            self.extension_cadence,
162        )
163    }
164}
165
166impl ReaderWriterLockProvider for RedisLockProvider {
167    type Lock = RedisDistributedReaderWriterLock;
168
169    fn create_reader_writer_lock(&self, name: &str) -> Self::Lock {
170        RedisDistributedReaderWriterLock::new(
171            name.to_string(),
172            self.clients.clone(),
173            self.expiry,
174            self.min_validity,
175            self.extension_cadence,
176        )
177    }
178}
179
180impl SemaphoreProvider for RedisLockProvider {
181    type Semaphore = RedisDistributedSemaphore;
182
183    fn create_semaphore(&self, name: &str, max_count: u32) -> Self::Semaphore {
184        RedisDistributedSemaphore::new(
185            name.to_string(),
186            max_count,
187            self.primary_client.clone(),
188            self.expiry,
189            self.extension_cadence,
190        )
191    }
192}