use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use lru::LruCache;
use tokio::sync::{RwLock, broadcast};
use crate::error::McError;
use crate::models::ServerStatus;
pub(crate) const DEFAULT_RESPONSE_CACHE_SIZE: usize = 256;
const INFLIGHT_CHANNEL_CAPACITY: usize = 64;
#[derive(Clone)]
struct Entry {
status: ServerStatus,
stored_at: SystemTime,
}
#[derive(Clone)]
pub struct ResponseCache {
entries: Arc<RwLock<LruCache<String, Entry>>>,
inflight: Arc<RwLock<std::collections::HashMap<
String,
broadcast::Sender<Result<ServerStatus, String>>,
>>>,
ttl: Duration,
}
impl ResponseCache {
pub fn disabled() -> Self {
Self::new(Duration::ZERO, 1)
}
pub fn new(ttl: Duration, capacity: usize) -> Self {
let cap = NonZeroUsize::new(capacity.max(1))
.expect("response cache capacity must be > 0");
Self {
entries: Arc::new(RwLock::new(LruCache::new(cap))),
inflight: Arc::new(RwLock::new(std::collections::HashMap::new())),
ttl,
}
}
pub fn is_enabled(&self) -> bool { !self.ttl.is_zero() }
pub fn ttl(&self) -> Duration { self.ttl }
pub async fn get(&self, key: &str) -> Option<ServerStatus> {
if !self.is_enabled() { return None; }
let mut cache = self.entries.write().await;
let entry = cache.get(key)?;
if entry.stored_at.elapsed().unwrap_or(Duration::MAX) >= self.ttl {
return None;
}
let mut status = entry.status.clone();
status.cached = true;
status.latency = 0.0;
Some(status)
}
pub async fn insert(&self, key: String, status: ServerStatus) {
if !self.is_enabled() { return; }
{
let entry = Entry { status: status.clone(), stored_at: SystemTime::now() };
self.entries.write().await.put(key.clone(), entry);
}
let sender = self.inflight.write().await.remove(&key);
if let Some(tx) = sender {
let _ = tx.send(Ok(status)); }
}
pub async fn insert_error(&self, key: &str, err: &McError) {
let sender = self.inflight.write().await.remove(key);
if let Some(tx) = sender {
let _ = tx.send(Err(err.to_string()));
}
}
pub async fn join_or_register(
&self,
key: &str,
) -> Option<broadcast::Receiver<Result<ServerStatus, String>>> {
if !self.is_enabled() { return None; }
let mut inflight = self.inflight.write().await;
if let Some(tx) = inflight.get(key) {
return Some(tx.subscribe());
}
let (tx, _) = broadcast::channel(INFLIGHT_CHANNEL_CAPACITY);
inflight.insert(key.to_string(), tx);
None
}
pub async fn clear(&self) {
self.entries.write().await.clear();
self.inflight.write().await.clear();
}
pub async fn len(&self) -> usize {
self.entries.read().await.len()
}
}