use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
fn claims() -> &'static Mutex<HashSet<PathBuf>> {
static CLAIMS: OnceLock<Mutex<HashSet<PathBuf>>> = OnceLock::new();
CLAIMS.get_or_init(|| Mutex::new(HashSet::new()))
}
#[derive(Debug)]
pub struct ProfileClaim {
dir: PathBuf,
}
impl ProfileClaim {
pub fn acquire(dir: &Path) -> Option<Self> {
let dir = dir.to_path_buf();
let mut held = claims().lock().unwrap_or_else(|e| e.into_inner());
if !held.insert(dir.clone()) {
return None;
}
Some(Self { dir })
}
pub fn dir(&self) -> &Path {
&self.dir
}
}
impl Drop for ProfileClaim {
fn drop(&mut self) {
claims()
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&self.dir);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dir(name: &str) -> PathBuf {
std::env::temp_dir().join(format!("car-profile-claim-test-{name}"))
}
#[test]
fn the_first_claim_on_a_directory_succeeds() {
let path = dir("first");
let claim = ProfileClaim::acquire(&path).expect("nothing holds it yet");
assert_eq!(claim.dir(), path.as_path());
}
#[test]
fn a_second_claim_on_a_held_directory_is_refused() {
let path = dir("second");
let _held = ProfileClaim::acquire(&path).expect("first");
assert!(
ProfileClaim::acquire(&path).is_none(),
"a live backend already holds this profile"
);
}
#[test]
fn releasing_a_claim_frees_the_directory_for_the_next_launch() {
let path = dir("release");
let claim = ProfileClaim::acquire(&path).expect("first");
drop(claim);
let again = ProfileClaim::acquire(&path);
assert!(
again.is_some(),
"a dropped backend must not hold its profile forever"
);
}
#[test]
fn different_directories_do_not_contend() {
let a = ProfileClaim::acquire(&dir("distinct-a")).expect("a");
let b = ProfileClaim::acquire(&dir("distinct-b")).expect("b");
assert_ne!(a.dir(), b.dir());
}
#[test]
fn a_poisoned_registry_still_serves_claims() {
let path = dir("poison");
let _ = std::panic::catch_unwind(|| {
let _guard = claims().lock().unwrap();
panic!("poison the registry");
});
assert!(ProfileClaim::acquire(&path).is_some());
}
}