#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Over {
#[default]
Shed,
Close,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rate {
pub per_sec: f64,
pub burst: u32,
pub over: Over,
}
impl Rate {
pub fn per_second(per_sec: f64) -> Self {
debug_assert!(per_sec.is_finite() && per_sec >= 0.0, "a rate is a finite count per second");
Self {
per_sec,
burst: per_sec.ceil().max(1.0) as u32,
over: Over::Shed,
}
}
pub fn burst(mut self, burst: u32) -> Self {
self.burst = burst;
self
}
pub fn disconnecting(mut self) -> Self {
self.over = Over::Close;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
Admit,
Shed,
Close,
}
#[derive(Debug)]
pub(crate) struct Bucket {
tokens: f64,
last_us: u64,
}
impl Bucket {
pub(crate) fn full(rate: Option<&Rate>, now_us: u64) -> Self {
Self {
tokens: rate.map_or(0.0, |r| f64::from(r.burst.max(1))),
last_us: now_us,
}
}
pub(crate) fn take(&mut self, rate: &Rate, now_us: u64) -> bool {
let ceiling = f64::from(rate.burst.max(1));
let elapsed = now_us.saturating_sub(self.last_us) as f64 / 1_000_000.0;
self.last_us = now_us;
self.tokens = (self.tokens + elapsed * rate.per_sec).min(ceiling);
if self.tokens >= 1.0 {
self.tokens -= 1.0;
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ms(t: u64) -> u64 {
t * 1_000
}
#[test]
fn a_burst_is_spent_and_then_the_rate_is_what_is_left() {
let rate = Rate::per_second(10.0).burst(5);
let mut bucket = Bucket::full(Some(&rate), 0);
for frame in 0..5 {
assert!(bucket.take(&rate, 0), "frame {frame} is inside the burst");
}
assert!(!bucket.take(&rate, 0), "and the sixth at the same instant is not");
assert!(bucket.take(&rate, ms(100)));
assert!(!bucket.take(&rate, ms(100)));
}
#[test]
fn an_idle_connection_does_not_bank_more_than_its_burst() {
let rate = Rate::per_second(10.0).burst(5);
let mut bucket = Bucket::full(Some(&rate), 0);
for _ in 0..5 {
bucket.take(&rate, 0);
}
let hour = ms(3_600_000);
let mut admitted = 0;
for _ in 0..100 {
if bucket.take(&rate, hour) {
admitted += 1;
}
}
assert_eq!(admitted, 5, "the bucket is capped at its burst, however long the silence");
}
#[test]
fn a_client_at_its_declared_rate_is_never_shed() {
let rate = Rate::per_second(60.0);
let mut bucket = Bucket::full(Some(&rate), 0);
for tick in 0..600u64 {
assert!(
bucket.take(&rate, ms(tick * 1_000 / 60)),
"tick {tick} is on the cadence the rate was written for"
);
}
}
#[test]
fn the_first_frame_of_a_connection_is_admitted() {
let rate = Rate::per_second(0.5);
let mut bucket = Bucket::full(Some(&rate), ms(7));
assert!(bucket.take(&rate, ms(7)));
}
}