decdn_node/load_shed/
controller.rs1use std::sync::Arc;
7
8use alloy::primitives::B256;
9use arc_swap::ArcSwap;
10use decdn_common::config::{LoadShedPolicyKind, ResolvedLoadShed};
11
12use super::{
13 AlwaysAdmit, EgressMeter, LoadShedPolicy, PressureSnapshot, RequestClass, ResourcePressure,
14 ResourcePressureParams, ShedDecision, ShedReason, ShedSlot, ShedState,
15};
16
17#[must_use]
19const fn egress_budget_bps(mbps: u64) -> u64 {
20 mbps.saturating_mul(125_000)
21}
22
23fn build_policy(cfg: &ResolvedLoadShed) -> Arc<dyn LoadShedPolicy> {
25 match cfg.policy {
26 LoadShedPolicyKind::AlwaysAdmit => Arc::new(AlwaysAdmit),
27 LoadShedPolicyKind::ResourcePressure => {
28 Arc::new(ResourcePressure::new(ResourcePressureParams {
29 egress_budget_bps: egress_budget_bps(cfg.egress_budget_mbps),
30 max_serves_high: cfg.max_concurrent_serves_high,
31 max_serves_low: cfg.max_concurrent_serves_low,
32 per_client_cap: cfg.per_client_serve_cap,
33 }))
34 }
35 }
36}
37
38#[allow(missing_debug_implementations)]
40pub struct LoadShedController {
41 policy: ArcSwap<Arc<dyn LoadShedPolicy>>,
47 state: Arc<ShedState>,
48 egress: Arc<EgressMeter>,
49}
50
51impl LoadShedController {
52 #[must_use]
55 pub fn from_config(cfg: &ResolvedLoadShed) -> Arc<Self> {
56 Arc::new(Self {
57 policy: ArcSwap::from_pointee(build_policy(cfg)),
58 state: ShedState::new(),
59 egress: Arc::new(EgressMeter::new()),
60 })
61 }
62
63 pub fn try_admit(&self, class: RequestClass, client: B256) -> Result<ShedSlot, ShedReason> {
66 let (node, per) = self.state.counts(client);
67 let snap = PressureSnapshot {
68 streams_in_flight: node,
69 client_streams_in_flight: per,
70 egress_bps: self.egress.current_bps(),
71 };
72 let policy = self.policy.load();
73 match policy.decide(class, &snap) {
74 ShedDecision::Admit => Ok(self.state.acquire(client)),
75 ShedDecision::Shed(reason) => Err(reason),
76 }
77 }
78
79 pub fn record_egress(&self, bytes: u64) {
83 self.egress.record(bytes);
84 }
85
86 pub fn sample_egress(&self, interval_secs: u64) -> u64 {
89 self.egress.sample(interval_secs)
90 }
91
92 #[must_use]
94 pub fn pressure_active(&self) -> bool {
95 self.policy.load().pressure_active()
96 }
97
98 #[must_use]
102 pub fn node_in_flight(&self) -> u32 {
103 self.state.node_in_flight()
104 }
105
106 pub fn reload(&self, cfg: &ResolvedLoadShed) {
109 self.policy.store(Arc::new(build_policy(cfg)));
110 }
111}
112
113#[cfg(test)]
114#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
115mod tests;