use std::collections::HashMap;
use std::path::Path;
use std::sync::{Arc, Mutex as StdMutex, OnceLock, Weak};
use tokio::sync::Mutex;
use crate::{StorageError, StorageResult};
static COMMIT_LOCKS: OnceLock<StdMutex<HashMap<String, Weak<Mutex<()>>>>> = OnceLock::new();
static DATASET_LOCKS: OnceLock<StdMutex<HashMap<String, Weak<StdMutex<()>>>>> = OnceLock::new();
pub(crate) fn weak_lookup<T>(
registry: &'static OnceLock<StdMutex<HashMap<String, Weak<T>>>>,
key: String,
fresh: Arc<T>,
) -> Arc<T> {
let mut map = registry
.get_or_init(|| StdMutex::new(HashMap::new()))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
if let Some(weak) = map.get(&key)
&& let Some(strong) = weak.upgrade()
{
return strong;
}
map.retain(|_, weak| weak.strong_count() > 0);
let arc = Arc::clone(&fresh);
map.insert(key, Arc::downgrade(&fresh));
arc
}
fn lock_for(metadata_path: &Path) -> Arc<Mutex<()>> {
let key = metadata_path.to_string_lossy().to_string();
weak_lookup(&COMMIT_LOCKS, key, Arc::new(Mutex::new(())))
}
fn dataset_lock_for(dataset_dir: &Path) -> Arc<StdMutex<()>> {
let key = dataset_dir.to_string_lossy().to_string();
weak_lookup(&DATASET_LOCKS, key, Arc::new(StdMutex::new(())))
}
pub(crate) async fn with_commit_actor<T, F, Fut>(
metadata_path: &Path,
commit: F,
) -> StorageResult<T>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = StorageResult<T>>,
{
let mailbox = lock_for(metadata_path);
let _guard = mailbox.lock().await;
commit().await
}
pub(crate) fn with_dataset_write_lock<T>(
dataset_dir: &Path,
write: impl FnOnce() -> StorageResult<T>,
) -> StorageResult<T> {
let mailbox = dataset_lock_for(dataset_dir);
let _guard = mailbox
.lock()
.map_err(|_| StorageError::InvalidState("dataset write lock poisoned".into()))?;
write()
}
#[cfg(test)]
pub(crate) fn registry_sizes() -> (usize, usize) {
let commit = COMMIT_LOCKS
.get()
.map(|m| m.lock().unwrap().len())
.unwrap_or(0);
let dataset = DATASET_LOCKS
.get()
.map(|m| m.lock().unwrap().len())
.unwrap_or(0);
(commit, dataset)
}