use super::{LOG_TARGET, now_ms};
use std::time::Duration;
use tinyufo::TinyUfo;
use tracing::debug;
#[derive(Debug, Clone)]
struct TtlLimit {
count: usize,
created_at: u64,
}
pub struct TtlLruLimit {
ttl: u64,
ufo: TinyUfo<String, TtlLimit>,
max: usize,
}
impl TtlLruLimit {
pub fn new(size: usize, ttl: Duration, max: usize) -> Self {
Self {
ttl: ttl.as_millis() as u64,
max,
ufo: TinyUfo::new(size, size),
}
}
pub fn new_compact(size: usize, ttl: Duration, max: usize) -> Self {
Self {
ttl: ttl.as_millis() as u64,
max,
ufo: TinyUfo::new_compact(size, size),
}
}
pub fn validate(&self, key: &str) -> bool {
let mut should_reset = false;
let mut valid = false;
let key = key.to_string();
if let Some(value) = self.ufo.get(&key) {
debug!(
target: LOG_TARGET,
key,
value = format!("{value:?}"),
"ttl lru limit"
);
if now_ms().saturating_sub(value.created_at) > self.ttl {
valid = true;
should_reset = true;
} else if value.count < self.max {
valid = true;
}
} else {
valid = true
}
if should_reset {
self.ufo.put(
key,
TtlLimit {
count: 0,
created_at: 0,
},
1,
);
}
valid
}
pub fn inc(&self, key: &str) {
let key = key.to_string();
let data = if let Some(mut value) = self.ufo.get(&key) {
if value.created_at == 0 {
value.created_at = now_ms();
}
value.count += 1;
value
} else {
TtlLimit {
count: 1,
created_at: now_ms(),
}
};
self.ufo.put(key, data, 1);
}
}
#[cfg(test)]
mod test {
use super::TtlLruLimit;
use pretty_assertions::assert_eq;
use std::time::Duration;
#[test]
fn test_ttl_lru_limit() {
for limit in [
TtlLruLimit::new(5, Duration::from_millis(500), 3),
TtlLruLimit::new_compact(5, Duration::from_millis(500), 3),
] {
coarsetime::Clock::update();
let key = "abc";
assert_eq!(true, limit.validate(key));
limit.inc(key);
limit.inc(key);
coarsetime::Clock::update();
assert_eq!(true, limit.validate(key));
limit.inc(key);
coarsetime::Clock::update();
assert_eq!(false, limit.validate(key));
std::thread::sleep(Duration::from_millis(600));
coarsetime::Clock::update();
assert_eq!(true, limit.validate(key));
}
}
}