use std::process; use std::sync::Arc; use export::{AppRedis, RedisConnection}; use uuid::Uuid; use crate::sync::error::SyncResult; const LOCK_KEY_PREFIX: &str = "git:sync:lock:"; const REFRESH_SCRIPT: &str = "local v = redis.call('GET', KEYS[1])\nif v == ARGV[1] then\n redis.call('PEXPIRE', KEYS[1], ARGV[2])\n return 1\nend\nreturn 0"; const RELEASE_SCRIPT: &str = "local v = redis.call('GET', KEYS[1])\nif v == ARGV[1] then\n return redis.call('DEL', KEYS[1])\nend\nreturn 0"; macro_rules! dispatch { ($conn:expr, |$c:ident| $body:expr) => { match &mut $conn { RedisConnection::Standalone($c) => $body, RedisConnection::Cluster($c) => $body, } }; } pub struct SyncLock { redis: AppRedis, key: String, value: String, } impl SyncLock { #[must_use] pub fn value(&self) -> &str { &self.value } #[must_use] pub fn key(&self) -> &str { &self.key } pub async fn refresh(&self, ttl_secs: u64) -> SyncResult { let mut conn = self.redis.connection_clone(); let n: i64 = dispatch!(conn, |c| { redis::cmd("EVAL") .arg(REFRESH_SCRIPT) .arg(1) .arg(&self.key) .arg(&self.value) .arg(ttl_secs.saturating_mul(1000)) .query_async(c) .await })?; Ok(n == 1) } pub async fn release(&self) -> SyncResult<()> { let mut conn = self.redis.connection_clone(); let _: i64 = dispatch!(conn, |c| { redis::cmd("EVAL") .arg(RELEASE_SCRIPT) .arg(1) .arg(&self.key) .arg(&self.value) .query_async(c) .await })?; Ok(()) } } fn lock_key(repo_id: Uuid) -> String { format!("{LOCK_KEY_PREFIX}{repo_id}") } pub async fn acquire_lock( redis: &AppRedis, repo_id: Uuid, ttl_secs: u64, ) -> SyncResult> { let key = lock_key(repo_id); let value = format!("{}:{}", Uuid::now_v7(), process::id()); let mut conn = redis.connection_clone(); let acquired: bool = dispatch!(conn, |c| { redis::cmd("SET") .arg(&key) .arg(&value) .arg("NX") .arg("EX") .arg(ttl_secs) .query_async(c) .await })?; if !acquired { return Ok(None); } Ok(Some(SyncLock { redis: redis.clone(), key, value, })) } #[allow(dead_code)] pub(crate) fn lock_handle(redis: AppRedis, key: String, value: String) -> SyncLock { SyncLock { redis, key, value } } #[allow(dead_code)] pub(crate) fn lock_key_for(repo_id: Uuid) -> Arc { Arc::from(lock_key(repo_id)) }