use std::sync::Arc;
use std::time::{Duration, Instant};
use systemprompt_traits::{CacheDomain, CacheEpochs};
use tokio::sync::RwLock;
use super::{ChainSources, ParentChainIndex};
use crate::authz::error::AuthzResult;
use crate::authz::repository::{AccessControlRepository, ChainFingerprint};
const DEFAULT_TTL: Duration = Duration::from_secs(60);
const DEFAULT_RECHECK: Duration = Duration::from_secs(5);
#[derive(Debug)]
struct CachedIndex {
index: Arc<ParentChainIndex>,
fingerprint: ChainFingerprint,
loaded_at: Instant,
checked_at: Instant,
epoch: u64,
}
#[derive(Debug)]
pub struct ChainIndexCache {
slot: RwLock<Option<CachedIndex>>,
ttl: Duration,
recheck: Duration,
epochs: CacheEpochs,
}
impl ChainIndexCache {
#[must_use]
pub fn new(ttl: Duration, recheck: Duration, epochs: CacheEpochs) -> Self {
Self {
slot: RwLock::new(None),
ttl,
recheck,
epochs,
}
}
#[must_use]
pub fn with_default_bounds(epochs: CacheEpochs) -> Self {
Self::new(DEFAULT_TTL, DEFAULT_RECHECK, epochs)
}
pub async fn get(
&self,
repo: &AccessControlRepository,
sources: Arc<ChainSources>,
) -> AuthzResult<Arc<ParentChainIndex>> {
let now = Instant::now();
let epoch = self.epochs.current(CacheDomain::Authz);
let fresh = {
let slot = self.slot.read().await;
slot.as_ref()
.filter(|cached| cached.epoch == epoch)
.filter(|cached| now.duration_since(cached.checked_at) < self.recheck)
.map(|cached| Arc::clone(&cached.index))
};
if let Some(index) = fresh {
return Ok(index);
}
{
let mut slot = self.slot.write().await;
if let Some(cached) = slot.as_mut().filter(|cached| cached.epoch == epoch) {
if now.duration_since(cached.checked_at) < self.recheck {
return Ok(Arc::clone(&cached.index));
}
if let Ok(fingerprint) = repo.chain_fingerprint().await
&& fingerprint == cached.fingerprint
&& now.duration_since(cached.loaded_at) < self.ttl
{
cached.checked_at = now;
return Ok(Arc::clone(&cached.index));
}
}
}
let fingerprint = repo.chain_fingerprint().await?;
let index = Arc::new(ParentChainIndex::load(repo, sources).await?);
*self.slot.write().await = Some(CachedIndex {
index: Arc::clone(&index),
fingerprint,
loaded_at: now,
checked_at: now,
epoch,
});
Ok(index)
}
}