use crate::*;
const N_BUCKET_PER_STEP_N_BIT: usize = 8;
const N_BUCKET_PER_STEP: usize = 1 << N_BUCKET_PER_STEP_N_BIT;
const TTL_BUCKET_INTERVAL_N_BIT_1: usize = 3;
const TTL_BUCKET_INTERVAL_N_BIT_2: usize = 7;
const TTL_BUCKET_INTERVAL_N_BIT_3: usize = 11;
const TTL_BUCKET_INTERVAL_N_BIT_4: usize = 15;
const TTL_BUCKET_INTERVAL_1: usize = 1 << TTL_BUCKET_INTERVAL_N_BIT_1;
const TTL_BUCKET_INTERVAL_2: usize = 1 << TTL_BUCKET_INTERVAL_N_BIT_2;
const TTL_BUCKET_INTERVAL_3: usize = 1 << TTL_BUCKET_INTERVAL_N_BIT_3;
const TTL_BUCKET_INTERVAL_4: usize = 1 << TTL_BUCKET_INTERVAL_N_BIT_4;
const TTL_BOUNDARY_1: i32 = 1 << (TTL_BUCKET_INTERVAL_N_BIT_1 + N_BUCKET_PER_STEP_N_BIT);
const TTL_BOUNDARY_2: i32 = 1 << (TTL_BUCKET_INTERVAL_N_BIT_2 + N_BUCKET_PER_STEP_N_BIT);
const TTL_BOUNDARY_3: i32 = 1 << (TTL_BUCKET_INTERVAL_N_BIT_3 + N_BUCKET_PER_STEP_N_BIT);
const MAX_N_TTL_BUCKET: usize = N_BUCKET_PER_STEP * 4;
const MAX_TTL_BUCKET_IDX: usize = MAX_N_TTL_BUCKET - 1;
pub struct TtlBuckets {
pub(crate) buckets: Box<[TtlBucket]>,
pub(crate) last_expired: Instant,
}
impl TtlBuckets {
pub fn new() -> Self {
let intervals = [
TTL_BUCKET_INTERVAL_1,
TTL_BUCKET_INTERVAL_2,
TTL_BUCKET_INTERVAL_3,
TTL_BUCKET_INTERVAL_4,
];
let mut buckets = Vec::with_capacity(0);
buckets.reserve_exact(intervals.len() * N_BUCKET_PER_STEP);
for interval in &intervals {
for j in 0..N_BUCKET_PER_STEP {
let ttl = interval * j + 1;
let bucket = TtlBucket::new(ttl as i32);
buckets.push(bucket);
}
}
let buckets = buckets.into_boxed_slice();
let last_expired = Instant::now();
Self {
buckets,
last_expired,
}
}
pub(crate) fn get_bucket_index(&self, ttl: Duration) -> usize {
let ttl = ttl.as_secs() as i32;
if ttl <= 0 {
self.buckets.len() - 1
} else if ttl & !(TTL_BOUNDARY_1 - 1) == 0 {
(ttl >> TTL_BUCKET_INTERVAL_N_BIT_1) as usize
} else if ttl & !(TTL_BOUNDARY_2 - 1) == 0 {
(ttl >> TTL_BUCKET_INTERVAL_N_BIT_2) as usize + N_BUCKET_PER_STEP
} else if ttl & !(TTL_BOUNDARY_3 - 1) == 0 {
(ttl >> TTL_BUCKET_INTERVAL_N_BIT_3) as usize + N_BUCKET_PER_STEP * 2
} else {
let bucket_idx = (ttl >> TTL_BUCKET_INTERVAL_N_BIT_4) as usize + N_BUCKET_PER_STEP * 3;
if bucket_idx > MAX_TTL_BUCKET_IDX {
MAX_TTL_BUCKET_IDX
} else {
bucket_idx
}
}
}
pub(crate) fn get_mut_bucket(&mut self, ttl: Duration) -> &mut TtlBucket {
let index = self.get_bucket_index(ttl);
unsafe { self.buckets.get_unchecked_mut(index) }
}
pub(crate) fn expire(&mut self, hashtable: &mut HashTable, segments: &mut Segments) -> usize {
let now = Instant::now();
if now == self.last_expired {
return 0;
} else {
self.last_expired = now;
}
let start = Instant::now();
let mut expired = 0;
for bucket in self.buckets.iter_mut() {
expired += bucket.expire(hashtable, segments);
}
let duration = start.elapsed();
debug!("expired: {expired} segments in {duration:?}");
#[cfg(feature = "metrics")]
EXPIRE_TIME.add(duration.as_nanos() as _);
expired
}
pub(crate) fn clear(&mut self, hashtable: &mut HashTable, segments: &mut Segments) -> usize {
let start = Instant::now();
let mut cleared = 0;
for bucket in self.buckets.iter_mut() {
cleared += bucket.clear(hashtable, segments);
}
let duration = start.elapsed();
debug!("expired: {cleared} segments in {duration:?}");
#[cfg(feature = "metrics")]
CLEAR_TIME.add(duration.as_nanos() as _);
cleared
}
}
impl Default for TtlBuckets {
fn default() -> Self {
Self::new()
}
}