use std::sync::Arc;
use dashmap::DashSet;
#[derive(Debug, Default, Clone)]
pub struct InFlightRegistry {
pr_keys: Arc<DashSet<String>>,
sha_keys: Arc<DashSet<String>>,
}
impl InFlightRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn try_acquire_pr(&self, owner: &str, repo: &str, pr: u64) -> Option<InFlightGuard> {
let key = format!("{owner}/{repo}/{pr}");
if self.pr_keys.insert(key.clone()) {
Some(InFlightGuard {
set: Arc::clone(&self.pr_keys),
key,
})
} else {
None
}
}
pub fn try_acquire_sha(
&self,
owner: &str,
repo: &str,
pr: u64,
sha: &str,
) -> Option<InFlightGuard> {
let key = format!("{owner}/{repo}/{pr}/{sha}");
if self.sha_keys.insert(key.clone()) {
Some(InFlightGuard {
set: Arc::clone(&self.sha_keys),
key,
})
} else {
None
}
}
}
#[derive(Debug)]
pub struct InFlightGuard {
set: Arc<DashSet<String>>,
key: String,
}
impl Drop for InFlightGuard {
fn drop(&mut self) {
self.set.remove(&self.key);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pr_guard_blocks_second() {
let reg = InFlightRegistry::new();
let g1 = reg.try_acquire_pr("acme", "backend", 42);
assert!(g1.is_some(), "first PR acquire must succeed");
let g2 = reg.try_acquire_pr("acme", "backend", 42);
assert!(g2.is_none(), "second PR acquire must be blocked");
drop(g1);
}
#[test]
fn pr_guard_released_on_drop() {
let reg = InFlightRegistry::new();
{
let _g = reg.try_acquire_pr("acme", "backend", 42);
} assert!(
reg.try_acquire_pr("acme", "backend", 42).is_some(),
"slot must be reusable after drop"
);
}
#[test]
fn different_pr_not_blocked() {
let reg = InFlightRegistry::new();
let _g1 = reg.try_acquire_pr("acme", "backend", 42);
assert!(reg.try_acquire_pr("acme", "backend", 43).is_some());
assert!(reg.try_acquire_pr("acme", "frontend", 42).is_some());
}
#[test]
fn sha_guard_independent_of_pr() {
let reg = InFlightRegistry::new();
let _pr = reg.try_acquire_pr("acme", "backend", 42);
let sha = reg.try_acquire_sha("acme", "backend", 42, "sha-abc");
assert!(sha.is_some(), "SHA slot is independent of PR slot");
assert!(
reg.try_acquire_sha("acme", "backend", 42, "sha-abc")
.is_none()
);
assert!(
reg.try_acquire_sha("acme", "backend", 42, "sha-def")
.is_some()
);
}
}