use std::collections::VecDeque;
use std::sync::{LazyLock, Mutex};
const MAX_PENDING: usize = 4096;
type Dropped = (String, String);
static DROPPED: LazyLock<Mutex<VecDeque<Dropped>>> = LazyLock::new(|| Mutex::new(VecDeque::new()));
pub(crate) fn push_dropped(namespace: String, key: String) {
let mut queue = DROPPED.lock().unwrap_or_else(|e| e.into_inner());
if queue.len() >= MAX_PENDING {
queue.pop_front();
}
queue.push_back((namespace, key));
}
pub fn drain_dropped_keys(namespace: &str) -> Vec<String> {
let mut queue = DROPPED.lock().unwrap_or_else(|e| e.into_inner());
let mut taken = Vec::new();
queue.retain(|(ns, key)| {
if ns == namespace {
taken.push(key.clone());
false
} else {
true
}
});
taken
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn guard() -> std::sync::MutexGuard<'static, ()> {
let g = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
DROPPED.lock().unwrap_or_else(|e| e.into_inner()).clear();
g
}
#[test]
fn drain_returns_only_the_requested_namespace() {
let _g = guard();
push_dropped("pool".into(), "a".into());
push_dropped("other".into(), "b".into());
push_dropped("pool".into(), "c".into());
assert_eq!(drain_dropped_keys("pool"), vec!["a", "c"]);
assert_eq!(drain_dropped_keys("other"), vec!["b"]);
}
#[test]
fn drain_is_exhaustive() {
let _g = guard();
push_dropped("pool".into(), "a".into());
assert_eq!(drain_dropped_keys("pool"), vec!["a"]);
assert!(drain_dropped_keys("pool").is_empty());
}
#[test]
fn queue_is_bounded_and_drops_oldest() {
let _g = guard();
for i in 0..MAX_PENDING + 10 {
push_dropped("pool".into(), i.to_string());
}
let drained = drain_dropped_keys("pool");
assert_eq!(drained.len(), MAX_PENDING);
assert_eq!(drained.first().map(String::as_str), Some("10"));
}
}