2ae9196
ZhenYi
feat(lib/git): add HTTP handler tree with routes and errors across analysis, archives, blobs, branches, commits, conflicts, content, diffs, hooks, lfs, logs, maintenance, packs, patches, pipelines, refs, remotes, repos, signatures, submodules, tags, trees
2ae9196
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<bool> {
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<Option<SyncLock>> {
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<str> {
Arc::from(lock_key(repo_id))
}