#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum OverloadState {
#[default]
Normal,
Emergency,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct OverloadConfig {
pub enter_drop_rate: f64,
pub recover_drop_rate: f64,
pub recover_windows: u32,
}
impl Default for OverloadConfig {
fn default() -> Self {
Self {
enter_drop_rate: 0.05,
recover_drop_rate: 0.01,
recover_windows: 3,
}
}
}
impl OverloadConfig {
pub fn enter_at(mut self, drop_rate: f64) -> Self {
self.enter_drop_rate = drop_rate;
self
}
pub fn recover_at(mut self, drop_rate: f64, windows: u32) -> Self {
self.recover_drop_rate = drop_rate;
self.recover_windows = windows.max(1);
self
}
}
#[derive(Debug, Clone)]
pub struct OverloadDetector {
config: OverloadConfig,
state: OverloadState,
calm_windows: u32,
}
impl OverloadDetector {
pub fn new(config: OverloadConfig) -> Self {
Self {
config,
state: OverloadState::Normal,
calm_windows: 0,
}
}
pub fn state(&self) -> OverloadState {
self.state
}
pub fn observe(&mut self, drop_rate: f64) -> Option<OverloadState> {
match self.state {
OverloadState::Normal => {
if drop_rate >= self.config.enter_drop_rate {
self.state = OverloadState::Emergency;
self.calm_windows = 0;
return Some(OverloadState::Emergency);
}
None
}
OverloadState::Emergency => {
if drop_rate < self.config.recover_drop_rate {
self.calm_windows += 1;
if self.calm_windows >= self.config.recover_windows {
self.state = OverloadState::Normal;
self.calm_windows = 0;
return Some(OverloadState::Normal);
}
} else {
self.calm_windows = 0;
}
None
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum ShedPolicy {
#[default]
Observe,
ShedNewFlows,
SampleFlows {
keep: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct SheddingStats {
pub admitted: u64,
pub shed: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShedDecision {
Admit,
Shed,
}
impl ShedDecision {
pub fn is_admitted(self) -> bool {
matches!(self, ShedDecision::Admit)
}
pub fn is_shed(self) -> bool {
matches!(self, ShedDecision::Shed)
}
}
#[derive(Debug, Clone)]
pub struct LoadShedder {
detector: OverloadDetector,
policy: ShedPolicy,
stats: SheddingStats,
}
impl LoadShedder {
pub fn new(config: OverloadConfig, policy: ShedPolicy) -> Self {
Self {
detector: OverloadDetector::new(config),
policy,
stats: SheddingStats::default(),
}
}
pub fn observe(&mut self, drop_rate: f64) -> Option<OverloadState> {
self.detector.observe(drop_rate)
}
pub fn state(&self) -> OverloadState {
self.detector.state()
}
pub fn policy(&self) -> ShedPolicy {
self.policy
}
pub fn stats(&self) -> SheddingStats {
self.stats
}
pub fn is_shedding(&self) -> bool {
self.state() == OverloadState::Emergency && self.policy != ShedPolicy::Observe
}
pub fn admit_new_flow(&mut self, flow_hash: u64) -> ShedDecision {
let decision = match (self.state(), self.policy) {
(OverloadState::Emergency, ShedPolicy::ShedNewFlows) => ShedDecision::Shed,
(OverloadState::Emergency, ShedPolicy::SampleFlows { keep }) => {
if unit_from_hash(flow_hash) < keep.clamp(0.0, 1.0) {
ShedDecision::Admit
} else {
ShedDecision::Shed
}
}
_ => ShedDecision::Admit,
};
match decision {
ShedDecision::Admit => self.stats.admitted += 1,
ShedDecision::Shed => self.stats.shed += 1,
}
decision
}
}
#[inline]
fn unit_from_hash(hash: u64) -> f64 {
(hash >> 11) as f64 / ((1u64 << 53) as f64)
}
#[cfg(test)]
mod tests {
use super::*;
fn det() -> OverloadDetector {
OverloadDetector::new(OverloadConfig::default())
}
#[test]
fn stays_normal_under_budget() {
let mut d = det();
for r in [0.0, 0.01, 0.04, 0.049] {
assert_eq!(d.observe(r), None);
}
assert_eq!(d.state(), OverloadState::Normal);
}
#[test]
fn enters_emergency_on_crossing() {
let mut d = det();
assert_eq!(d.observe(0.06), Some(OverloadState::Emergency));
assert_eq!(d.state(), OverloadState::Emergency);
assert_eq!(d.observe(0.20), None);
}
#[test]
fn requires_sustained_calm_to_recover() {
let mut d = det();
d.observe(0.10); assert_eq!(d.observe(0.005), None);
assert_eq!(d.observe(0.005), None);
assert_eq!(d.observe(0.005), Some(OverloadState::Normal));
}
#[test]
fn a_spike_resets_the_calm_counter_no_flapping() {
let mut d = det();
d.observe(0.10); d.observe(0.005); d.observe(0.005); d.observe(0.08); assert_eq!(d.state(), OverloadState::Emergency);
assert_eq!(d.observe(0.0), None);
assert_eq!(d.observe(0.0), None);
assert_eq!(d.observe(0.0), Some(OverloadState::Normal));
}
#[test]
fn between_recover_and_enter_holds_emergency() {
let mut d = det();
d.observe(0.10); for _ in 0..10 {
assert_eq!(d.observe(0.03), None);
}
assert_eq!(d.state(), OverloadState::Emergency);
}
fn shedder(policy: ShedPolicy) -> LoadShedder {
LoadShedder::new(OverloadConfig::default(), policy)
}
#[test]
fn unit_from_hash_is_in_range_and_monotone() {
assert_eq!(unit_from_hash(0), 0.0);
assert!(unit_from_hash(u64::MAX) < 1.0);
assert!(unit_from_hash(u64::MAX) > 0.999);
assert!(unit_from_hash(1u64 << 62) > unit_from_hash(1u64 << 60));
}
#[test]
fn normal_state_always_admits_regardless_of_policy() {
for policy in [
ShedPolicy::Observe,
ShedPolicy::ShedNewFlows,
ShedPolicy::SampleFlows { keep: 0.0 },
] {
let mut s = shedder(policy);
assert_eq!(s.state(), OverloadState::Normal);
for h in 0..1000 {
assert!(s.admit_new_flow(h).is_admitted());
}
assert_eq!(s.stats().shed, 0);
assert_eq!(s.stats().admitted, 1000);
assert!(!s.is_shedding());
}
}
#[test]
fn observe_policy_never_sheds_even_in_emergency() {
let mut s = shedder(ShedPolicy::Observe);
assert_eq!(s.observe(0.10), Some(OverloadState::Emergency));
for h in 0..1000 {
assert!(s.admit_new_flow(h).is_admitted());
}
assert_eq!(s.stats().shed, 0);
assert!(!s.is_shedding(), "Observe is never actively shedding");
}
#[test]
fn shed_new_flows_drops_all_new_in_emergency() {
let mut s = shedder(ShedPolicy::ShedNewFlows);
s.observe(0.10); assert!(s.is_shedding());
for h in 0..500 {
assert!(s.admit_new_flow(h).is_shed());
}
assert_eq!(s.stats().shed, 500);
assert_eq!(s.stats().admitted, 0);
}
#[test]
fn sample_flows_admits_roughly_keep_fraction_in_emergency() {
let mut s = shedder(ShedPolicy::SampleFlows { keep: 0.25 });
s.observe(0.10); let n = 10_000u64;
for i in 0..n {
let h = i.wrapping_mul(0x9E37_79B9_7F4A_7C15); s.admit_new_flow(h);
}
let admitted = s.stats().admitted as f64 / n as f64;
assert!(
(admitted - 0.25).abs() < 0.03,
"admitted fraction {admitted} should be ~0.25",
);
}
#[test]
fn sample_flows_is_deterministic_per_hash() {
let mut a = shedder(ShedPolicy::SampleFlows { keep: 0.5 });
let mut b = shedder(ShedPolicy::SampleFlows { keep: 0.5 });
a.observe(0.10);
b.observe(0.10);
for h in 0..1000 {
assert_eq!(
a.admit_new_flow(h),
b.admit_new_flow(h),
"same hash must yield same verdict (both legs share a fate)",
);
}
}
#[test]
fn keep_is_clamped() {
let mut hi = shedder(ShedPolicy::SampleFlows { keep: 2.0 });
hi.observe(0.10);
let mut lo = shedder(ShedPolicy::SampleFlows { keep: -1.0 });
lo.observe(0.10);
for h in 0..200 {
assert!(hi.admit_new_flow(h).is_admitted());
assert!(lo.admit_new_flow(h).is_shed());
}
}
#[test]
fn admission_resumes_after_recovery() {
let mut s = shedder(ShedPolicy::ShedNewFlows);
s.observe(0.10); assert!(s.admit_new_flow(1).is_shed());
s.observe(0.0);
s.observe(0.0);
assert_eq!(s.observe(0.0), Some(OverloadState::Normal));
assert!(!s.is_shedding());
assert!(s.admit_new_flow(1).is_admitted());
}
}