use std::borrow::Borrow;
use std::fmt;
use std::hash::BuildHasher;
use std::hash::Hash;
use std::hash::RandomState;
use std::sync::Arc;
use hashbrown::HashTable;
use crate::internal::mutex::Mutex;
use crate::once::OnceCell;
#[cfg(test)]
mod tests;
struct Entry<K, V> {
hash: u64,
key: K,
cell: OnceCell<V>,
}
pub struct Group<K, V, S = RandomState> {
entries: Mutex<HashTable<Arc<Entry<K, V>>>>,
hasher: S,
}
impl<K, V, S> fmt::Debug for Group<K, V, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let in_flight = self.entries.lock().len();
f.debug_struct("Group")
.field("in_flight", &in_flight)
.finish()
}
}
impl<K, V, S> Group<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
fn get_or_insert(&self, key: K) -> Arc<Entry<K, V>> {
let hash = self.hasher.hash_one(&key);
let mut entries = self.entries.lock();
entries
.entry(hash, |entry| entry.key.eq(&key), |entry| entry.hash)
.or_insert_with(|| {
Arc::new(Entry {
hash,
key,
cell: OnceCell::new(),
})
})
.into_mut()
.clone()
}
fn remove<Q>(&self, key: &Q)
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
let hash = self.hasher.hash_one(key);
let removed = {
let mut entries = self.entries.lock();
let Ok(occupied) = entries.find_entry(hash, |entry| entry.key.borrow() == key) else {
return;
};
occupied.remove().0
};
drop(removed);
}
fn remove_if_current(&self, entry: &Arc<Entry<K, V>>) {
let removed = {
let mut entries = self.entries.lock();
let Ok(occupied) = entries.find_entry(entry.hash, |stored| Arc::ptr_eq(stored, entry))
else {
return;
};
occupied.remove().0
};
drop(removed);
}
fn cleanup_abandoned_entry(&self, entry: Arc<Entry<K, V>>) {
let mut entries = self.entries.lock();
let Ok(occupied) = entries.find_entry(entry.hash, |stored| Arc::ptr_eq(stored, &entry))
else {
drop(entries);
drop(entry);
return;
};
if Arc::strong_count(&entry) == 2 && !entry.cell.initialized() {
let (stored, _) = occupied.remove();
drop(entries);
drop(entry);
drop(stored);
} else {
drop(entry);
}
}
}
struct WorkCleanupGuard<'a, K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
group: &'a Group<K, V, S>,
entry: Option<Arc<Entry<K, V>>>,
}
impl<'a, K, V, S> WorkCleanupGuard<'a, K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
fn new(group: &'a Group<K, V, S>, key: K) -> Self {
let entry = group.get_or_insert(key);
Self {
group,
entry: Some(entry),
}
}
fn entry(&self) -> &Arc<Entry<K, V>> {
self.entry.as_ref().unwrap()
}
fn dismiss(mut self) {
drop(self.entry.take());
}
}
impl<K, V, S> Drop for WorkCleanupGuard<'_, K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
fn drop(&mut self) {
let Some(entry) = self.entry.take() else {
return;
};
self.group.cleanup_abandoned_entry(entry);
}
}
impl<K, V, S> Default for Group<K, V, S>
where
K: Eq + Hash,
V: Clone,
S: BuildHasher + Default,
{
fn default() -> Self {
Self::with_hasher(S::default())
}
}
impl<K, V> Group<K, V, RandomState>
where
K: Eq + Hash,
V: Clone,
{
pub fn new() -> Self {
Self::with_hasher(RandomState::new())
}
}
impl<K, V, S> Group<K, V, S>
where
K: Eq + Hash,
V: Clone,
S: BuildHasher,
{
pub fn with_hasher(hasher: S) -> Self {
Self {
entries: Mutex::new(HashTable::new()),
hasher,
}
}
pub async fn work<F>(&self, key: K, func: F) -> V
where
F: AsyncFnOnce() -> V,
{
let guard = WorkCleanupGuard::new(self, key);
let entry = guard.entry();
let result = entry
.cell
.get_or_init(async || {
let result = func().await;
self.remove_if_current(entry);
result
})
.await
.clone();
guard.dismiss();
result
}
pub async fn try_work<E, F>(&self, key: K, func: F) -> Result<V, E>
where
F: AsyncFnOnce() -> Result<V, E>,
{
let guard = WorkCleanupGuard::new(self, key);
let entry = guard.entry();
let result = entry
.cell
.get_or_try_init(async || {
let result = func().await?;
self.remove_if_current(entry);
Ok(result)
})
.await?
.clone();
guard.dismiss();
Ok(result)
}
pub fn forget<Q>(&self, key: &Q)
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.remove(key);
}
}