Skip to main content

decdn_node/load_shed/
state.rs

1//! Live in-flight serve counters: one node-wide count and one per client,
2//! generalizing the per-lane atomic-count admission technique to a node-wide
3//! resource gate. A [`ShedSlot`] holds one admitted stream's place and releases
4//! it on every exit — success, error, `?`, disconnect, panic — via `Drop`.
5
6use std::sync::Arc;
7use std::sync::atomic::{AtomicU32, Ordering};
8
9use alloy::primitives::B256;
10use dashmap::DashMap;
11
12/// Node-wide and per-client in-flight serve counts. Held in an `Arc` so a
13/// [`ShedSlot`] can outlive the borrow that created it.
14#[derive(Debug, Default)]
15pub struct ShedState {
16    node_active: AtomicU32,
17    per_client: DashMap<B256, Arc<AtomicU32>>,
18}
19
20impl ShedState {
21    /// A counter with nothing in flight, already wrapped so [`ShedSlot`]s can
22    /// hold it.
23    #[must_use]
24    pub fn new() -> Arc<Self> {
25        Arc::new(Self::default())
26    }
27
28    /// `(node_in_flight, client_in_flight)`. A client with no entry counts 0.
29    #[must_use]
30    pub fn counts(&self, client: B256) -> (u32, u32) {
31        let node = self.node_active.load(Ordering::Relaxed);
32        let per = self
33            .per_client
34            .get(&client)
35            .map_or(0, |c| c.load(Ordering::Relaxed));
36        (node, per)
37    }
38
39    /// Node-wide serves in flight across all clients — the same count
40    /// [`Self::counts`] returns as its first element, without needing a client
41    /// key. Sampled onto the `decdn_load_shed_streams_in_flight` gauge so an
42    /// operator can read live concurrency against the shed high-water mark.
43    #[must_use]
44    pub fn node_in_flight(&self) -> u32 {
45        self.node_active.load(Ordering::Relaxed)
46    }
47
48    /// Increment both counters and hand back the RAII slot.
49    #[must_use]
50    pub fn acquire(self: &Arc<Self>, client: B256) -> ShedSlot {
51        self.node_active.fetch_add(1, Ordering::Relaxed);
52        let counter = self
53            .per_client
54            .entry(client)
55            .or_insert_with(|| Arc::new(AtomicU32::new(0)))
56            .clone();
57        counter.fetch_add(1, Ordering::Relaxed);
58        ShedSlot {
59            state: Arc::clone(self),
60            client,
61            counter,
62        }
63    }
64}
65
66/// RAII place for one admitted serve. `Drop` releases the node-wide and
67/// per-client counts on every exit path, so a finished stream always frees its
68/// slot; the count is owned here, never decremented by hand.
69#[derive(Debug)]
70pub struct ShedSlot {
71    state: Arc<ShedState>,
72    client: B256,
73    counter: Arc<AtomicU32>,
74}
75
76impl Drop for ShedSlot {
77    fn drop(&mut self) {
78        self.state
79            .node_active
80            .update(Ordering::Relaxed, Ordering::Relaxed, |n| {
81                n.saturating_sub(1)
82            });
83        self.counter
84            .update(Ordering::Relaxed, Ordering::Relaxed, |n| {
85                n.saturating_sub(1)
86            });
87        // Best-effort prune: only removes if whatever is currently mapped for
88        // this key is zero; a concurrently-recreated live entry is left intact
89        // (remove_if re-checks under the shard lock).
90        self.state
91            .per_client
92            .remove_if(&self.client, |_, c| c.load(Ordering::Relaxed) == 0);
93    }
94}
95
96#[cfg(test)]
97mod tests;