use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Mutex, RwLock};
use std::time::{Duration, Instant};
type ChangeFn<K> = dyn Fn(&K, bool) + Send + Sync;
pub struct Rendezvous<K, V> {
inner: RwLock<HashMap<K, (V, Instant)>>,
ttl: Duration,
on_change: Mutex<Option<Box<ChangeFn<K>>>>,
}
pub const DEFAULT_TTL: Duration = Duration::from_secs(300);
impl<K: Eq + Hash + Clone, V> Default for Rendezvous<K, V> {
fn default() -> Self {
Self::new(DEFAULT_TTL)
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct RendezvousSnapshot<K, V> {
pub entries: Vec<(K, V, u64)>,
}
impl<K: Eq + Hash + Clone, V> Rendezvous<K, V> {
pub fn new(ttl: Duration) -> Self {
Self { inner: RwLock::new(HashMap::new()), ttl, on_change: Mutex::new(None) }
}
fn read(&self) -> std::sync::RwLockReadGuard<'_, HashMap<K, (V, Instant)>> {
self.inner.read().unwrap_or_else(|e| e.into_inner())
}
fn write(&self) -> std::sync::RwLockWriteGuard<'_, HashMap<K, (V, Instant)>> {
self.inner.write().unwrap_or_else(|e| e.into_inner())
}
pub fn on_change<F>(&self, f: F)
where
F: Fn(&K, bool) + Send + Sync + 'static,
{
*self.on_change.lock().unwrap_or_else(|e| e.into_inner()) = Some(Box::new(f));
}
fn fire(&self, key: &K, present: bool) {
if let Some(cb) = self.on_change.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
cb(key, present);
}
}
pub fn issue(&self, key: K, value: V) {
self.issue_with_ttl(key, value, self.ttl);
}
pub fn issue_with_ttl(&self, key: K, value: V, ttl: Duration) {
let deadline = Instant::now() + ttl;
{
self.write().insert(key.clone(), (value, deadline));
}
self.fire(&key, true);
}
pub fn claim(&self, key: &K) -> Option<V> {
let now = Instant::now();
let taken = {
let mut g = self.write();
g.remove(key)
};
match taken {
Some((v, deadline)) => {
self.fire(key, false);
if now < deadline {
Some(v)
} else {
None }
}
None => None,
}
}
pub fn sweep(&self) {
let now = Instant::now();
let expired: Vec<K> = {
let g = self.read();
g.iter().filter(|(_, (_, d))| now >= *d).map(|(k, _)| k.clone()).collect()
};
if !expired.is_empty() {
let mut g = self.write();
for k in &expired {
g.remove(k);
}
drop(g);
for k in &expired {
self.fire(k, false);
}
}
}
pub fn len(&self) -> usize {
self.read().len()
}
pub fn is_empty(&self) -> bool {
self.read().is_empty()
}
}
impl<K: Eq + Hash + Clone, V: Clone> Rendezvous<K, V> {
pub fn peek(&self, key: &K) -> Option<V> {
let now = Instant::now();
let g = self.read();
match g.get(key) {
Some((v, deadline)) if now < *deadline => Some(v.clone()),
_ => None,
}
}
pub fn snapshot(&self) -> RendezvousSnapshot<K, V> {
let now = Instant::now();
let g = self.read();
let entries = g
.iter()
.filter_map(|(k, (v, deadline))| {
let remaining = deadline.saturating_duration_since(now);
if remaining.is_zero() {
None
} else {
Some((k.clone(), v.clone(), remaining.as_millis() as u64))
}
})
.collect();
RendezvousSnapshot { entries }
}
pub fn restore(&self, snap: RendezvousSnapshot<K, V>) {
let now = Instant::now();
let mut g = self.write();
g.clear();
for (k, v, remaining_ms) in snap.entries {
g.insert(k, (v, now + Duration::from_millis(remaining_ms)));
}
}
}