use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, OwnedMutexGuard};
#[derive(Debug, Clone, Default)]
pub struct PathLocks {
inner: Arc<Mutex<HashMap<String, Arc<Mutex<()>>>>>,
}
impl PathLocks {
pub fn new() -> Self {
Self::default()
}
pub async fn guard(&self, key: &str) -> OwnedMutexGuard<()> {
let lock = {
let mut registry = self.inner.lock().await;
registry
.entry(key.to_string())
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone()
};
lock.lock_owned().await
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn same_key_calls_serialise() {
let locks = PathLocks::new();
let g1 = locks.guard("p").await;
let locks2 = locks.clone();
let task = tokio::spawn(async move {
let _g2 = locks2.guard("p").await;
});
tokio::time::sleep(Duration::from_millis(20)).await;
assert!(
!task.is_finished(),
"second guard must block while first is held"
);
drop(g1);
tokio::time::timeout(Duration::from_millis(200), task)
.await
.expect("second guard must resolve once first drops")
.unwrap();
}
#[tokio::test]
async fn distinct_keys_do_not_serialise() {
let locks = PathLocks::new();
let g1 = locks.guard("key-a").await;
let _g2 = tokio::time::timeout(Duration::from_millis(50), locks.guard("key-b"))
.await
.expect("distinct key must not block");
drop(g1);
}
}