rust_zero_core/
balancer.rs1use std::{
2 fmt,
3 sync::{
4 atomic::{AtomicU64, AtomicUsize, Ordering},
5 Arc, Mutex,
6 },
7 time::{Duration, Instant},
8};
9
10const DECAY_TIME: Duration = Duration::from_secs(10);
11const FORCE_PICK_AFTER: Duration = Duration::from_secs(1);
12
13struct NodeState {
14 latency_micros: f64,
15 success: f64,
16 last_update: Instant,
17 last_pick: Instant,
18}
19
20struct Node<T> {
21 value: T,
22 inflight: AtomicUsize,
23 requests: AtomicU64,
24 state: Mutex<NodeState>,
25}
26
27impl<T> Node<T> {
28 fn new(value: T) -> Self {
29 let now = Instant::now();
30 Self {
31 value,
32 inflight: AtomicUsize::new(0),
33 requests: AtomicU64::new(0),
34 state: Mutex::new(NodeState {
35 latency_micros: 0.0,
36 success: 1.0,
37 last_update: now,
38 last_pick: now,
39 }),
40 }
41 }
42
43 fn load(&self) -> f64 {
44 let state = self.state.lock().expect("P2C node state lock poisoned");
45 let latency = state.latency_micros.max(1.0).sqrt();
46 latency * (self.inflight.load(Ordering::Relaxed) as f64 + 1.0)
47 }
48
49 fn healthy(&self) -> bool {
50 self.state
51 .lock()
52 .expect("P2C node state lock poisoned")
53 .success
54 > 0.5
55 }
56
57 fn mark_picked(&self) {
58 self.state
59 .lock()
60 .expect("P2C node state lock poisoned")
61 .last_pick = Instant::now();
62 self.inflight.fetch_add(1, Ordering::Relaxed);
63 self.requests.fetch_add(1, Ordering::Relaxed);
64 }
65
66 fn complete(&self, started: Instant, success: bool) {
67 self.inflight.fetch_sub(1, Ordering::Relaxed);
68 let now = Instant::now();
69 let mut state = self.state.lock().expect("P2C node state lock poisoned");
70 let elapsed = now.saturating_duration_since(state.last_update);
71 let weight = (-elapsed.as_secs_f64() / DECAY_TIME.as_secs_f64()).exp();
72 let latency = now.saturating_duration_since(started).as_micros() as f64;
73
74 state.latency_micros = if state.latency_micros == 0.0 {
75 latency
76 } else {
77 state.latency_micros * weight + latency * (1.0 - weight)
78 };
79 let outcome = if success { 1.0 } else { 0.0 };
80 state.success = state.success * weight + outcome * (1.0 - weight);
81 state.last_update = now;
82 }
83}
84
85pub struct P2cBalancer<T> {
90 nodes: Arc<[Arc<Node<T>>]>,
91 random: AtomicU64,
92}
93
94impl<T> P2cBalancer<T> {
95 pub fn new(nodes: impl IntoIterator<Item = T>) -> Result<Self, BalancerError> {
96 let nodes: Vec<_> = nodes
97 .into_iter()
98 .map(|node| Arc::new(Node::new(node)))
99 .collect();
100 if nodes.is_empty() {
101 return Err(BalancerError::Empty);
102 }
103
104 Ok(Self {
105 nodes: nodes.into(),
106 random: AtomicU64::new(0x4d59_5df4_d0f3_3173),
107 })
108 }
109
110 pub fn len(&self) -> usize {
111 self.nodes.len()
112 }
113
114 pub fn is_empty(&self) -> bool {
115 self.nodes.is_empty()
116 }
117
118 pub fn pick(&self) -> P2cRequest<T> {
119 let selected = match self.nodes.len() {
120 1 => Arc::clone(&self.nodes[0]),
121 len => {
122 let first = self.next_index(len);
123 let mut second = self.next_index(len - 1);
124 if second >= first {
125 second += 1;
126 }
127 choose(&self.nodes[first], &self.nodes[second])
128 }
129 };
130
131 selected.mark_picked();
132 P2cRequest {
133 node: Some(selected),
134 started: Instant::now(),
135 }
136 }
137
138 fn next_index(&self, len: usize) -> usize {
139 let mut current = self.random.load(Ordering::Relaxed);
140 loop {
141 let mut next = current;
142 next ^= next << 13;
143 next ^= next >> 7;
144 next ^= next << 17;
145 match self.random.compare_exchange_weak(
146 current,
147 next,
148 Ordering::Relaxed,
149 Ordering::Relaxed,
150 ) {
151 Ok(_) => return next as usize % len,
152 Err(actual) => current = actual,
153 }
154 }
155 }
156
157 pub fn snapshots(&self) -> Vec<NodeSnapshot<'_, T>> {
158 self.nodes
159 .iter()
160 .map(|node| {
161 let state = node.state.lock().expect("P2C node state lock poisoned");
162 NodeSnapshot {
163 value: &node.value,
164 inflight: node.inflight.load(Ordering::Relaxed),
165 requests: node.requests.load(Ordering::Relaxed),
166 latency: Duration::from_secs_f64(state.latency_micros / 1_000_000.0),
167 success_rate: state.success,
168 }
169 })
170 .collect()
171 }
172}
173
174fn choose<T>(first: &Arc<Node<T>>, second: &Arc<Node<T>>) -> Arc<Node<T>> {
175 let first_healthy = first.healthy();
176 let second_healthy = second.healthy();
177 if first_healthy != second_healthy {
178 return Arc::clone(if first_healthy { first } else { second });
179 }
180
181 {
182 let state = second.state.lock().expect("P2C node state lock poisoned");
183 if state.last_pick.elapsed() > FORCE_PICK_AFTER {
184 return Arc::clone(second);
185 }
186 }
187
188 Arc::clone(if first.load() <= second.load() {
189 first
190 } else {
191 second
192 })
193}
194
195pub struct P2cRequest<T> {
197 node: Option<Arc<Node<T>>>,
198 started: Instant,
199}
200
201impl<T> P2cRequest<T> {
202 pub fn value(&self) -> &T {
203 &self
204 .node
205 .as_ref()
206 .expect("P2C request already completed")
207 .value
208 }
209
210 pub fn complete(mut self, success: bool) {
211 if let Some(node) = self.node.take() {
212 node.complete(self.started, success);
213 }
214 }
215}
216
217impl<T> Drop for P2cRequest<T> {
218 fn drop(&mut self) {
219 if let Some(node) = self.node.take() {
220 node.complete(self.started, false);
221 }
222 }
223}
224
225#[derive(Debug)]
226pub struct NodeSnapshot<'a, T> {
227 pub value: &'a T,
228 pub inflight: usize,
229 pub requests: u64,
230 pub latency: Duration,
231 pub success_rate: f64,
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum BalancerError {
236 Empty,
237}
238
239impl fmt::Display for BalancerError {
240 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
241 formatter.write_str("a P2C balancer requires at least one node")
242 }
243}
244
245impl std::error::Error for BalancerError {}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn rejects_an_empty_pool() {
253 assert!(matches!(
254 P2cBalancer::<&str>::new([]),
255 Err(BalancerError::Empty)
256 ));
257 }
258
259 #[test]
260 fn tracks_inflight_requests_and_completions() {
261 let balancer = P2cBalancer::new(["one"]).unwrap();
262 let request = balancer.pick();
263 assert_eq!(request.value(), &"one");
264 assert_eq!(balancer.snapshots()[0].inflight, 1);
265
266 request.complete(true);
267 let snapshot = balancer.snapshots();
268 assert_eq!(snapshot[0].inflight, 0);
269 assert_eq!(snapshot[0].requests, 1);
270 assert!(snapshot[0].success_rate > 0.5);
271 }
272
273 #[test]
274 fn dropping_a_request_records_a_failure() {
275 let balancer = P2cBalancer::new(["one"]).unwrap();
276 drop(balancer.pick());
277
278 let snapshot = balancer.snapshots();
279 assert_eq!(snapshot[0].inflight, 0);
280 assert!(snapshot[0].success_rate < 1.0);
281 }
282}