use crate::lib::*;
use crate::{
algorithms::{Algorithm, NonConformance, RateLimitState},
clock,
thread_safety::ThreadsafeWrapper,
InconsistentCapacity, NegativeMultiDecision,
};
#[cfg(feature = "std")]
mod std {
use crate::clock;
use evmap::ShallowCopy;
impl<P: clock::Reference> ShallowCopy for super::State<P> {
unsafe fn shallow_copy(&mut self) -> Self {
super::State(self.0.shallow_copy())
}
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
struct Tat<P: clock::Reference>(Option<P>);
impl<P: clock::Reference> Default for Tat<P> {
fn default() -> Self {
Tat(None)
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct State<P: clock::Reference>(ThreadsafeWrapper<Tat<P>>);
impl<P: clock::Reference> Default for State<P> {
fn default() -> Self {
State(Default::default())
}
}
impl<P: clock::Reference> RateLimitState<GCRA<P>, P> for State<P> {
fn last_touched(&self, params: &GCRA<P>) -> Option<P> {
let data = self.0.snapshot();
Some(data.0? + params.tau)
}
}
#[derive(Debug, PartialEq)]
pub struct NotUntil<P: clock::Reference>(P);
impl<P: clock::Reference> fmt::Display for NotUntil<P> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "rate-limited until {:?}", self.0)
}
}
impl<P: clock::Reference> NonConformance<P> for NotUntil<P> {
#[inline]
fn earliest_possible(&self) -> P {
self.0
}
}
#[derive(Debug, Clone)]
pub struct GCRA<P: clock::Reference = <clock::DefaultClock as clock::Clock>::Instant> {
t: Duration,
tau: Duration,
point: PhantomData<P>,
}
impl<P: clock::Reference> Algorithm<P> for GCRA<P> {
type BucketState = State<P>;
type NegativeDecision = NotUntil<P>;
fn construct(
capacity: NonZeroU32,
cell_weight: NonZeroU32,
per_time_unit: Duration,
) -> Result<Self, InconsistentCapacity> {
if capacity < cell_weight {
return Err(InconsistentCapacity::new(capacity, cell_weight));
}
Ok(GCRA {
t: (per_time_unit / capacity.get()) * cell_weight.get(),
tau: per_time_unit,
point: PhantomData,
})
}
fn test_and_update(
&self,
state: &Self::BucketState,
t0: P,
) -> Result<(), Self::NegativeDecision> {
let tau = self.tau;
let t = self.t;
state.0.measure_and_replace(|tat| {
let tat = tat.0.unwrap_or(t0);
if t0 < tat.saturating_sub(tau) {
(Err(NotUntil(tat)), None)
} else {
(Ok(()), Some(Tat(Some(cmp::max(tat, t0) + t))))
}
})
}
fn test_n_and_update(
&self,
state: &Self::BucketState,
n: u32,
t0: P,
) -> Result<(), NegativeMultiDecision<Self::NegativeDecision>> {
let tau = self.tau;
let t = self.t;
state.0.measure_and_replace(|tat| {
let tat = tat.0.unwrap_or(t0);
let tat = match n {
0 => t0,
1 => tat,
_ => {
let weight = t * (n - 1);
if (weight + t) > tau {
return (Err(NegativeMultiDecision::InsufficientCapacity(n)), None);
}
tat + weight
}
};
let additional_weight = match n {
0 => Duration::new(0, 0),
1 => t,
_ => t * n,
};
if t0 < tat.saturating_sub(tau) {
(
Err(NegativeMultiDecision::BatchNonConforming(n, NotUntil(tat))),
None,
)
} else {
(
Ok(()),
Some(Tat(Some(cmp::max(tat, t0) + additional_weight))),
)
}
})
}
}