use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct TtlQueue<T> {
buf: VecDeque<(u64, T)>,
capacity: usize,
}
impl<T> TtlQueue<T> {
pub fn new(capacity: usize) -> Self {
assert!(capacity > 0, "TtlQueue capacity must be > 0");
Self {
buf: VecDeque::with_capacity(capacity),
capacity,
}
}
pub fn push(&mut self, now: u64, expires_at: u64, item: T) {
self.evict(now);
if self.buf.len() >= self.capacity {
self.buf.pop_front();
}
self.buf.push_back((expires_at, item));
}
pub fn pop(&mut self, now: u64) -> Option<T> {
self.evict(now);
self.buf.pop_front().map(|(_, item)| item)
}
pub fn peek(&mut self, now: u64) -> Option<&T> {
self.evict(now);
self.buf.front().map(|(_, item)| item)
}
pub fn drain(&mut self, now: u64) -> Vec<T> {
self.evict(now);
self.buf.drain(..).map(|(_, item)| item).collect()
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
fn evict(&mut self, now: u64) {
while let Some(&(expires_at, _)) = self.buf.front() {
if expires_at > 0 && expires_at <= now {
self.buf.pop_front();
} else {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_pop_basic() {
let mut q = TtlQueue::new(8);
q.push(100, 200, "a");
q.push(100, 200, "b");
assert_eq!(q.pop(100), Some("a"));
assert_eq!(q.pop(100), Some("b"));
assert_eq!(q.pop(100), None);
}
#[test]
fn expired_items_evicted() {
let mut q = TtlQueue::new(8);
q.push(100, 150, "old");
q.push(100, 300, "fresh");
assert_eq!(q.pop(200), Some("fresh"));
assert!(q.is_empty());
}
#[test]
fn zero_ttl_never_expires() {
let mut q = TtlQueue::new(4);
q.push(100, 0, "forever");
assert_eq!(q.pop(u64::MAX), Some("forever"));
}
#[test]
fn capacity_drops_oldest() {
let mut q = TtlQueue::new(2);
q.push(100, 0, "a");
q.push(100, 0, "b");
q.push(100, 0, "c"); assert_eq!(q.len(), 2);
assert_eq!(q.pop(100), Some("b"));
assert_eq!(q.pop(100), Some("c"));
}
#[test]
fn drain_returns_all_non_expired() {
let mut q = TtlQueue::new(8);
q.push(100, 150, "expired");
q.push(100, 300, "ok1");
q.push(100, 0, "ok2");
let items = q.drain(200);
assert_eq!(items, vec!["ok1", "ok2"]);
assert!(q.is_empty());
}
#[test]
fn peek_does_not_remove() {
let mut q = TtlQueue::new(4);
q.push(100, 0, "x");
assert_eq!(q.peek(100), Some(&"x"));
assert_eq!(q.len(), 1);
}
#[test]
#[should_panic(expected = "capacity must be > 0")]
fn zero_capacity_panics() {
TtlQueue::<u8>::new(0);
}
}