use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};
pub const QUOTA_EXHAUSTION_TTL: Duration = Duration::from_secs(60 * 60);
fn registry() -> &'static Mutex<HashMap<String, Instant>> {
static REG: OnceLock<Mutex<HashMap<String, Instant>>> = OnceLock::new();
REG.get_or_init(|| Mutex::new(HashMap::new()))
}
fn key(provider: &str) -> String {
provider.to_lowercase()
}
pub fn mark_exhausted(provider: &str) {
mark_exhausted_for(provider, QUOTA_EXHAUSTION_TTL);
}
pub fn mark_exhausted_for(provider: &str, ttl: Duration) {
if provider.is_empty() {
return;
}
if let Ok(mut map) = registry().lock() {
map.insert(key(provider), Instant::now() + ttl);
tracing::warn!(
"Provider '{}' marked quota-exhausted for {}s (#952)",
provider,
ttl.as_secs()
);
}
}
pub fn is_exhausted(provider: &str) -> bool {
let Ok(mut map) = registry().lock() else {
return false;
};
let k = key(provider);
match map.get(&k) {
Some(until) if *until > Instant::now() => true,
Some(_) => {
map.remove(&k);
false
}
None => false,
}
}
pub fn clear(provider: &str) {
if let Ok(mut map) = registry().lock() {
map.remove(&key(provider));
}
}
pub fn clear_all() {
if let Ok(mut map) = registry().lock() {
map.clear();
}
}
pub fn exhausted_snapshot() -> Vec<String> {
let Ok(mut map) = registry().lock() else {
return Vec::new();
};
let now = Instant::now();
map.retain(|_, until| *until > now);
let mut names: Vec<String> = map.keys().cloned().collect();
names.sort();
names
}