- pool/worker.rs: single-threaded consumer that BLMPOPs from Redis queues sequentially. K8s replicas provide HA — each pod runs one worker. - pool/redis.rs: RedisConsumer with BLMOVE atomic dequeue, ACK/NAK, and retry-with-json support. - pool/types.rs: HookTask, TaskType, PoolConfig (minimal — no pool metrics). - sync/lock.rs: Redis SET NX EX per-repo lock to prevent concurrent workers from processing the same repo. Lock conflicts are handled by requeueing without incrementing retry count. - hook/mod.rs: HookService.start_worker() spawns the background worker. - ssh/mod.rs / http/mod.rs: ReceiveSyncService RPUSHes to Redis queue. Both run_http and run_ssh call start_worker() to launch the consumer. - Lock conflicts (GitError::Locked) in the worker are requeued without incrementing retry_count so another worker can pick them up.
67 lines
2.1 KiB
Rust
67 lines
2.1 KiB
Rust
use crate::GitError;
|
|
use crate::hook::sync::HookMetaDataSync;
|
|
|
|
impl HookMetaDataSync {
|
|
const LOCK_TTL_SECS: u64 = 60;
|
|
|
|
/// Try to acquire an exclusive lock for this repo.
|
|
/// Returns the lock value if acquired, which must be passed to `release_lock`.
|
|
pub async fn acquire_lock(&self) -> Result<String, GitError> {
|
|
let lock_key = format!("git:repo:lock:{}", self.repo.id);
|
|
let lock_value = format!("{}:{}", uuid::Uuid::new_v4(), std::process::id());
|
|
|
|
let mut conn = self
|
|
.cache
|
|
.conn()
|
|
.await
|
|
.map_err(|e| GitError::IoError(format!("failed to get redis connection: {}", e)))?;
|
|
|
|
let result: bool = redis::cmd("SET")
|
|
.arg(&lock_key)
|
|
.arg(&lock_value)
|
|
.arg("NX")
|
|
.arg("EX")
|
|
.arg(Self::LOCK_TTL_SECS)
|
|
.query_async(&mut conn)
|
|
.await
|
|
.map_err(|e| GitError::IoError(format!("failed to acquire lock: {}", e)))?;
|
|
|
|
if result {
|
|
Ok(lock_value)
|
|
} else {
|
|
Err(GitError::Locked(format!(
|
|
"repository {} is locked by another process",
|
|
self.repo.id
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// Release the lock, but only if we still own it (value matches).
|
|
pub async fn release_lock(&self, lock_value: &str) -> Result<(), GitError> {
|
|
let lock_key = format!("git:repo:lock:{}", self.repo.id);
|
|
|
|
let mut conn = self
|
|
.cache
|
|
.conn()
|
|
.await
|
|
.map_err(|e| GitError::IoError(format!("failed to get redis connection: {}", e)))?;
|
|
|
|
let script = r#"
|
|
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
return redis.call("del", KEYS[1])
|
|
else
|
|
return 0
|
|
end
|
|
"#;
|
|
|
|
let _: i32 = redis::Script::new(script)
|
|
.key(&lock_key)
|
|
.arg(lock_value)
|
|
.invoke_async(&mut conn)
|
|
.await
|
|
.map_err(|e| GitError::IoError(format!("failed to release lock: {}", e)))?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|