use std::cell::{Cell};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::marker::PhantomData;
use crate::{Quota, QuotaRef, QuotaResult};
use crate::QuotaPolicy;
pub struct StaleQuotaRef;
pub struct QuotaPool<'a> {
initial_available_tokens: u64,
policy: QuotaPolicy,
key_map: Cell<HashMap<&'static str, usize>>,
quotas: Cell<Vec<Quota>>,
last_access_times_ns: Cell<Vec<std::time::Instant>>,
last_refill_times_ns: Cell<Vec<std::time::Instant>>,
epoch: Cell<std::time::Instant>,
generations: Cell<Vec<u32>>,
occupied: Cell<Vec<bool>>,
_marker: PhantomData<&'a ()>,
}
impl<'a> QuotaPool<'a> {
#[inline]
pub fn new(policy: QuotaPolicy, initial_available_tokens: u64) -> Self {
Self {
initial_available_tokens,
policy,
key_map: Cell::new(HashMap::new()),
quotas: Cell::new(Vec::new()),
last_access_times_ns: Cell::new(Vec::new()),
last_refill_times_ns: Cell::new(Vec::new()),
epoch: Cell::new(std::time::Instant::now()),
generations: Cell::new(Vec::new()),
occupied: Cell::new(Vec::new()),
_marker: PhantomData,
}
}
#[inline]
pub fn reset(&mut self) {
let now = std::time::Instant::now();
self.epoch = Cell::new(now);
self.last_access_times_ns.get_mut().fill(now);
}
#[inline]
pub fn quota(&'a self, key: &'static str) -> QuotaRef<'a> {
unsafe {
let key_map = &mut *self.key_map.as_ptr();
let quotas = &mut *self.quotas.as_ptr();
match key_map.entry(key) {
Entry::Occupied(occupied_entry) => {
QuotaRef {
pool: &self,
index: *occupied_entry.get(),
generation: 0
}
}
Entry::Vacant(vacant_entry) => {
let last_access_times_ns = &mut *self.last_access_times_ns.as_ptr();
let last_refill_times_ns = &mut *self.last_refill_times_ns.as_ptr();
let new_index = quotas.len();
vacant_entry.insert(new_index);
quotas.push(Quota::with_initial_tokens(self.initial_available_tokens));
let t = std::time::Instant::now();
last_access_times_ns.push(t);
last_refill_times_ns.push(t);
QuotaRef {
pool: &self,
index: new_index,
generation: 0
}
}
}
}
}
#[inline]
pub(crate) fn quota_with_index(&self, key: &'static str) -> (&Quota, usize) {
unsafe {
let key_map = &mut *self.key_map.as_ptr();
let quotas = &mut *self.quotas.as_ptr();
match key_map.entry(key) {
Entry::Occupied(occupied_entry) => {
let i = *occupied_entry.get();
("as[i], i)
}
Entry::Vacant(vacant_entry) => {
let last_access_times_ns = &mut *self.last_access_times_ns.as_ptr();
let last_refill_times_ns = &mut *self.last_refill_times_ns.as_ptr();
let new_index = quotas.len();
vacant_entry.insert(new_index);
quotas.push(Quota::with_initial_tokens(self.initial_available_tokens));
let t = std::time::Instant::now();
last_access_times_ns.push(t);
last_refill_times_ns.push(t);
("as.get_unchecked(new_index), new_index)
}
}
}
}
#[inline]
pub fn consume(&self, key: &'static str, cost: u64) -> QuotaResult {
let (quota, index) = self.quota_with_index(key);
let last_access_time_ns = unsafe { (&mut *self.last_access_times_ns.as_ptr())[index] };
let delta = last_access_time_ns.elapsed();
self.policy.tick(delta.as_nanos() as u64, "a);
quota.consume(cost)
}
#[inline]
pub(crate) fn consume_at(
&self,
index: usize,
generation: u32,
cost: u64,
) -> Result<QuotaResult, StaleQuotaRef> {
unsafe {
let quotas = &mut *self.quotas.as_ptr();
let generations = &*self.generations.as_ptr();
let occupied = &*self.occupied.as_ptr();
let last_access_times = &mut *self.last_access_times_ns.as_ptr();
if index >= quotas.len()
|| generations[index] != generation
|| !occupied[index]
{
return Err(StaleQuotaRef);
}
let now = std::time::Instant::now();
let dt = now.duration_since(last_access_times[index]);
last_access_times[index] = now;
self.policy.tick(dt.as_nanos() as u64, "as[index]);
Ok(quotas[index].consume(cost))
}
}
}
#[cfg(test)]
mod tests;