use std::collections::HashMap;
use std::sync::{Arc, OnceLock, RwLock};
use std::time::Duration;
use pensieve_core::catalog::{Catalog, TenantQuota};
use pensieve_core::tenant::TenantId;
fn cache() -> &'static RwLock<HashMap<TenantId, TenantQuota>> {
static C: OnceLock<RwLock<HashMap<TenantId, TenantQuota>>> = OnceLock::new();
C.get_or_init(|| RwLock::new(HashMap::new()))
}
pub async fn refresh(catalog: &Arc<dyn Catalog>) {
if let Ok(rows) = catalog.list_tenant_quotas().await {
let map: HashMap<TenantId, TenantQuota> =
rows.into_iter().map(|q| (q.tenant, q)).collect();
if let Ok(mut g) = cache().write() {
*g = map;
}
}
}
pub fn query_limit_override(tenant: TenantId) -> Option<u32> {
cache().read().ok()?.get(&tenant)?.max_query_concurrent
}
pub fn agent_limit_override(tenant: TenantId) -> Option<u32> {
cache().read().ok()?.get(&tenant)?.max_agent_concurrent
}
pub fn spawn_refresh(catalog: Arc<dyn Catalog>) -> tokio::task::JoinHandle<()> {
let secs = std::env::var("PENSIEVE_QUOTA_REFRESH_SECS")
.ok()
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(30)
.max(1);
tokio::spawn(async move {
loop {
refresh(&catalog).await;
tokio::time::sleep(Duration::from_secs(secs)).await;
}
})
}
#[cfg(any(test, feature = "test-support"))]
pub fn set_for_test(quota: TenantQuota) {
if let Ok(mut g) = cache().write() {
g.insert(quota.tenant, quota);
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn clear_for_test() {
if let Ok(mut g) = cache().write() {
g.clear();
}
}