use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock, Weak};
use tokio::sync::{Mutex as AsyncMutex, OwnedMutexGuard};
type SaveLocks = HashMap<(PathBuf, String), Weak<AsyncMutex<()>>>;
pub(crate) async fn acquire(dir: &Path, id: &str) -> OwnedMutexGuard<()> {
static LOCKS: OnceLock<Mutex<SaveLocks>> = OnceLock::new();
let lock = {
let mut locks = LOCKS
.get_or_init(Default::default)
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
locks.retain(|_, lock| lock.strong_count() != 0);
let key = (dir.to_path_buf(), id.to_owned());
if let Some(lock) = locks.get(&key).and_then(Weak::upgrade) {
lock
} else {
let lock = Arc::new(AsyncMutex::new(()));
locks.insert(key, Arc::downgrade(&lock));
lock
}
};
lock.lock_owned().await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn detached_writer_keeps_save_order_until_blocking_io_finishes() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path().to_path_buf();
let guard = acquire(&dir, "session").await;
let (release, wait) = std::sync::mpsc::channel();
let (started, ready) = tokio::sync::oneshot::channel();
let writer = tokio::task::spawn_blocking(move || {
let _guard = guard;
started.send(()).unwrap();
wait.recv().unwrap();
});
ready.await.unwrap();
drop(writer); let next = tokio::spawn(async move { acquire(&dir, "session").await });
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
assert!(!next.is_finished());
release.send(()).unwrap();
let _guard = tokio::time::timeout(std::time::Duration::from_secs(2), next)
.await
.unwrap()
.unwrap();
}
}