use redis::{Client, Connection, RedisResult};
use std::error::Error;
use std::time::Duration;
use uuid::Uuid;
pub struct MultiResourceLock {
conn: Connection,
}
impl std::fmt::Debug for MultiResourceLock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MultiResourceLock")
.field("conn", &"..")
.finish()
}
}
pub fn setup(client: &redis::Client) -> Result<(), Box<dyn Error>> {
let mut con = client.get_connection()?;
let lua_library = include_str!("functions.lua");
let result: String = redis::cmd("FUNCTION")
.arg("LOAD")
.arg("REPLACE")
.arg(lua_library)
.query(&mut con)?;
println!("Lua library loaded: {}", result);
Ok(())
}
impl MultiResourceLock {
pub fn new(client: &Client) -> RedisResult<Self> {
let conn = client.get_connection()?;
Ok(MultiResourceLock { conn })
}
pub fn acquire(
&mut self,
resources: &[String],
expiration: Duration,
timeout: Duration,
sleep: Duration,
) -> RedisResult<Option<String>> {
let now = std::time::Instant::now();
loop {
if now.elapsed() > timeout {
return Ok(None);
}
match self.try_acquire(resources, expiration)? {
Some(res) => break Ok(Some(res)),
None => std::thread::sleep(sleep),
}
}
}
pub fn try_acquire(
&mut self,
resources: &[String],
expiration: Duration,
) -> RedisResult<Option<String>> {
let lock_id = Uuid::new_v4().to_string();
let mut args = vec![lock_id.clone(), expiration.as_millis().to_string()];
args.extend(resources.iter().cloned());
let result: Option<String> = redis::cmd("FCALL")
.arg("acquire_lock")
.arg(&args)
.query(&mut self.conn)?;
Ok(result)
}
pub fn release(&mut self, lock_id: &str) -> RedisResult<usize> {
let result: usize = redis::cmd("FCALL")
.arg("release_lock")
.arg(lock_id)
.query(&mut self.conn)?;
Ok(result)
}
pub fn try_lock(
&mut self,
resources: &[String],
expiration: Duration,
) -> RedisResult<Option<MultiResourceGuard<'_>>> {
self.try_acquire(resources, expiration).map(|result| {
result.map(|lock_id| MultiResourceGuard {
lock: self,
lock_id,
})
})
}
pub fn lock(
&mut self,
resources: &[String],
expiration: Duration,
timeout: Duration,
sleep: Duration,
) -> RedisResult<Option<MultiResourceGuard<'_>>> {
self.acquire(resources, expiration, timeout, sleep)
.map(|result| {
result.map(|lock_id| MultiResourceGuard {
lock: self,
lock_id,
})
})
}
}
#[derive(Debug)]
pub struct MultiResourceGuard<'a> {
lock: &'a mut MultiResourceLock,
lock_id: String,
}
impl Drop for MultiResourceGuard<'_> {
fn drop(&mut self) {
self.lock.release(&self.lock_id).unwrap();
}
}