#![cfg(feature = "std")]
use crate::lib::*;
use evmap::{self, ReadHandle, WriteHandle};
use parking_lot::Mutex;
use crate::{
algorithms::{Algorithm, DefaultAlgorithm, KeyableRateLimitState, RateLimitState},
clock,
clock::Reference,
InconsistentCapacity, NegativeMultiDecision,
};
type MapWriteHandle<K, C, A, H> =
Arc<Mutex<WriteHandle<K, <A as Algorithm<<C as clock::Clock>::Instant>>::BucketState, (), H>>>;
#[derive(Clone)]
pub struct KeyedRateLimiter<
K: Eq + Hash + Clone,
A: Algorithm<C::Instant> = DefaultAlgorithm,
C: clock::Clock = clock::DefaultClock,
H: BuildHasher + Clone = RandomState,
> where
A::BucketState: KeyableRateLimitState<A, C::Instant>,
{
algorithm: A,
map_reader: ReadHandle<K, A::BucketState, (), H>,
map_writer: MapWriteHandle<K, C, A, H>,
clock: C,
}
impl<A, K, C: clock::Clock> fmt::Debug for KeyedRateLimiter<K, A, C>
where
A: Algorithm<C::Instant>,
A::BucketState: KeyableRateLimitState<A, C::Instant>,
K: Eq + Hash + Clone,
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "KeyedRateLimiter{{{params:?}}}", params = self.algorithm)
}
}
impl<C, A, K> KeyedRateLimiter<K, A, C>
where
C: clock::Clock,
A: Algorithm<C::Instant>,
A::BucketState: KeyableRateLimitState<A, C::Instant>,
K: Eq + Hash + Clone,
{
pub fn new(capacity: NonZeroU32, per_time_unit: Duration) -> Self {
let (r, mut w): (
ReadHandle<K, A::BucketState>,
WriteHandle<K, A::BucketState>,
) = evmap::new();
w.refresh();
KeyedRateLimiter {
algorithm: <A as Algorithm<C::Instant>>::construct(
capacity,
nonzero!(1u32),
per_time_unit,
)
.unwrap(),
map_reader: r,
map_writer: Arc::new(Mutex::new(w)),
clock: Default::default(),
}
}
pub fn per_second(capacity: NonZeroU32) -> Self {
Self::new(capacity, Duration::from_secs(1))
}
pub fn build_with_capacity(capacity: NonZeroU32) -> Builder<K, C, A, RandomState> {
Builder {
capacity,
..Default::default()
}
}
fn check_and_update_key<E, F>(&self, key: K, update: F) -> Result<(), E>
where
F: Fn(&A::BucketState) -> Result<(), E>,
{
self.map_reader
.get_and(&key, |v| {
let state = &v[0];
update(state)
})
.unwrap_or_else(|| {
let mut w = self.map_writer.lock();
let state: A::BucketState = Default::default();
let result = update(&state);
w.update(key, state);
w.flush();
result
})
}
pub fn check(&mut self, key: K) -> Result<(), <A as Algorithm<C::Instant>>::NegativeDecision> {
self.check_at(key, self.clock.now())
}
pub fn check_n(
&mut self,
key: K,
n: u32,
) -> Result<(), NegativeMultiDecision<<A as Algorithm<C::Instant>>::NegativeDecision>> {
self.check_n_at(key, n, self.clock.now())
}
pub fn check_at(
&mut self,
key: K,
at: C::Instant,
) -> Result<(), <A as Algorithm<C::Instant>>::NegativeDecision> {
self.check_and_update_key(key, |state| self.algorithm.test_and_update(state, at))
}
pub fn check_n_at(
&mut self,
key: K,
n: u32,
at: C::Instant,
) -> Result<(), NegativeMultiDecision<<A as Algorithm<C::Instant>>::NegativeDecision>> {
self.check_and_update_key(key, |state| self.algorithm.test_n_and_update(state, n, at))
}
pub fn cleanup<D: Into<Option<Duration>>>(&mut self, min_age: D) -> Vec<K> {
self.cleanup_at(min_age, self.clock.now())
}
pub fn cleanup_at<D: Into<Option<Duration>>, I: Into<Option<C::Instant>>>(
&mut self,
min_age: D,
at: I,
) -> Vec<K> {
let params = &self.algorithm;
let min_age = min_age.into().unwrap_or_else(|| Duration::new(0, 0));
let at = at.into().unwrap_or_else(|| self.clock.now());
let mut expireable: Vec<K> = vec![];
self.map_reader.for_each(|k, v| {
if let Some(state) = v.get(0) {
if state
.last_touched(params)
.unwrap_or_else(|| self.clock.now())
< at.saturating_sub(min_age)
{
expireable.push(k.clone());
}
}
});
let mut w = self.map_writer.lock();
for key in expireable.iter().cloned() {
w.empty(key);
}
w.refresh();
expireable
}
}
pub struct Builder<K: Eq + Hash + Clone, C: clock::Clock, A: Algorithm<C::Instant>, H: BuildHasher>
{
end_result: PhantomData<(K, A)>,
clock: C,
capacity: NonZeroU32,
cell_weight: NonZeroU32,
per_time_unit: Duration,
hasher: H,
map_capacity: Option<usize>,
}
impl<K, A, C> Default for Builder<K, C, A, RandomState>
where
K: Eq + Hash + Clone,
C: clock::Clock,
A: Algorithm<C::Instant>,
A::BucketState: KeyableRateLimitState<A, C::Instant>,
{
fn default() -> Builder<K, C, A, RandomState> {
Builder {
end_result: PhantomData,
clock: Default::default(),
map_capacity: None,
capacity: nonzero!(1u32),
cell_weight: nonzero!(1u32),
per_time_unit: Duration::from_secs(1),
hasher: RandomState::new(),
}
}
}
impl<K, C, A, H> Builder<K, C, A, H>
where
K: Eq + Hash + Clone,
C: clock::Clock,
A: Algorithm<C::Instant>,
A::BucketState: KeyableRateLimitState<A, C::Instant>,
H: BuildHasher,
{
pub fn with_hasher<H2: BuildHasher>(self, hash_builder: H2) -> Builder<K, C, A, H2> {
Builder {
hasher: hash_builder,
clock: Default::default(),
end_result: self.end_result,
capacity: self.capacity,
cell_weight: self.cell_weight,
per_time_unit: self.per_time_unit,
map_capacity: self.map_capacity,
}
}
pub fn with_cell_weight(self, cell_weight: NonZeroU32) -> Result<Self, InconsistentCapacity> {
if self.cell_weight > self.capacity {
return Err(InconsistentCapacity::new(self.capacity, cell_weight));
}
Ok(Builder {
cell_weight,
..self
})
}
pub fn with_map_capacity(self, map_capacity: usize) -> Self {
Builder {
map_capacity: Some(map_capacity),
..self
}
}
pub fn using_clock(self, clock: C) -> Self {
Builder { clock, ..self }
}
pub fn build(self) -> Result<KeyedRateLimiter<K, A, C, H>, InconsistentCapacity>
where
H: Clone,
{
let map_opts = evmap::Options::default().with_hasher(self.hasher);
let (r, mut w) = if self.map_capacity.is_some() {
map_opts
.with_capacity(self.map_capacity.unwrap())
.construct()
} else {
map_opts.construct()
};
w.refresh();
Ok(KeyedRateLimiter {
algorithm: <A as Algorithm<C::Instant>>::construct(
self.capacity,
self.cell_weight,
self.per_time_unit,
)?,
clock: self.clock,
map_reader: r,
map_writer: Arc::new(Mutex::new(w)),
})
}
}