use crate::OxidArt;
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
pub type SharedArt = Rc<RefCell<OxidArt>>;
impl OxidArt {
pub fn shared_with_ticker(interval: Duration) -> SharedArt {
let art = Rc::new(RefCell::new(Self::new()));
art.borrow_mut().tick(); spawn_ticker(art.clone(), interval);
art
}
pub fn shared_with_evictor(tick_interval: Duration, evict_interval: Duration) -> SharedArt {
let art = Rc::new(RefCell::new(Self::new()));
art.borrow_mut().tick(); spawn_ticker(art.clone(), tick_interval);
spawn_evictor(art.clone(), evict_interval);
art
}
#[inline]
pub fn tick(&mut self) {
self.now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system time before UNIX epoch")
.as_secs();
}
}
pub fn spawn_ticker(art: Rc<RefCell<OxidArt>>, interval: Duration) {
monoio::spawn(async move {
loop {
monoio::time::sleep(interval).await;
art.borrow_mut().tick();
}
});
}
pub fn spawn_evictor(art: Rc<RefCell<OxidArt>>, interval: Duration) {
monoio::spawn(async move {
loop {
monoio::time::sleep(interval).await;
art.borrow_mut().evict_expired();
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
#[monoio::test(enable_timer = true)]
async fn test_ttl_expiration_with_ticker() {
let art = Rc::new(RefCell::new(OxidArt::new()));
spawn_ticker(art.clone(), Duration::from_millis(100));
art.borrow_mut().tick();
art.borrow_mut().set_ttl(
Bytes::from_static(b"batch:1"),
Duration::from_secs(1),
Bytes::from_static(b"expires_soon"),
);
art.borrow_mut().set(
Bytes::from_static(b"batch:2"),
Bytes::from_static(b"forever"),
);
let results = art.borrow().getn(Bytes::from_static(b"batch:"));
assert_eq!(results.len(), 2, "should have 2 entries before expiration");
monoio::time::sleep(Duration::from_secs(2)).await;
monoio::time::sleep(Duration::from_millis(150)).await;
let results = art.borrow().getn(Bytes::from_static(b"batch:"));
assert_eq!(results.len(), 1, "should have 1 entry after expiration");
assert_eq!(
results[0],
(
Bytes::from_static(b"batch:2"),
Bytes::from_static(b"forever")
)
);
}
#[monoio::test(enable_timer = true)]
async fn test_shared_with_ticker_constructor() {
let art = OxidArt::shared_with_ticker(Duration::from_millis(100));
art.borrow_mut().set_ttl(
Bytes::from_static(b"test"),
Duration::from_secs(1),
Bytes::from_static(b"value"),
);
assert!(art.borrow_mut().get(Bytes::from_static(b"test")).is_some());
monoio::time::sleep(Duration::from_secs(2)).await;
monoio::time::sleep(Duration::from_millis(150)).await;
assert!(art.borrow_mut().get(Bytes::from_static(b"test")).is_none());
}
#[monoio::test(enable_timer = true)]
async fn test_spawn_evictor_100_entries() {
let art = Rc::new(RefCell::new(OxidArt::new()));
art.borrow_mut().set_now(1000);
for i in 0..100 {
let key = Bytes::from(format!("key:{:03}", i));
let val = Bytes::from(format!("value:{:03}", i));
art.borrow_mut().set_ttl(key, Duration::from_secs(10), val);
}
let all_entries = art.borrow().getn(Bytes::from_static(b"key:"));
assert_eq!(all_entries.len(), 100, "should have 100 entries initially");
spawn_evictor(art.clone(), Duration::from_millis(1));
monoio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(
art.borrow().getn(Bytes::from_static(b"key:")).len(),
100,
"no entries should be evicted yet"
);
art.borrow_mut().set_now(1011);
monoio::time::sleep(Duration::from_millis(100)).await;
let remaining = art.borrow().getn(Bytes::from_static(b"key:"));
assert_eq!(
remaining.len(),
0,
"all entries should be evicted, but {} remain",
remaining.len()
);
}
#[monoio::test(enable_timer = true)]
async fn test_spawn_evictor_partial_expiration() {
let art = Rc::new(RefCell::new(OxidArt::new()));
art.borrow_mut().set_now(1000);
for i in 0..50 {
let key = Bytes::from(format!("short:{:03}", i));
let val = Bytes::from(format!("value:{:03}", i));
art.borrow_mut().set_ttl(key, Duration::from_secs(10), val);
}
for i in 0..50 {
let key = Bytes::from(format!("long:{:03}", i));
let val = Bytes::from(format!("value:{:03}", i));
art.borrow_mut().set_ttl(key, Duration::from_secs(100), val);
}
assert_eq!(art.borrow().getn(Bytes::from_static(b"")).len(), 100);
spawn_evictor(art.clone(), Duration::from_millis(1));
art.borrow_mut().set_now(1011);
monoio::time::sleep(Duration::from_millis(100)).await;
let remaining = art.borrow().getn(Bytes::from_static(b""));
assert_eq!(remaining.len(), 50, "50 long entries should remain");
for (key, _) in &remaining {
assert!(
key.starts_with(b"long:"),
"remaining key should be long: {:?}",
key
);
}
art.borrow_mut().set_now(1101);
monoio::time::sleep(Duration::from_millis(100)).await;
assert_eq!(art.borrow().getn(Bytes::from_static(b"")).len(), 0);
}
}