Skip to main content

load_balancer/
fault_tolerant.rs

1use crate::LoadBalancer;
2use std::sync::atomic::Ordering::{Acquire, Release};
3use std::{
4    future::Future,
5    sync::{Arc, atomic::AtomicU64},
6    time::{Duration, Instant},
7};
8use tokio::{
9    spawn,
10    sync::{Mutex, RwLock},
11    task::{JoinHandle, yield_now},
12    time::sleep,
13};
14
15/// Represents a single entry in the fault-tolerant load balancer.
16/// Tracks the maximum allowed requests, maximum errors, current usage count, and error count.
17pub struct Entry<T>
18where
19    T: Send + Sync + Clone + 'static,
20{
21    /// Maximum number of allocations per interval.
22    pub max_count: u64,
23    /// Maximum number of allowed errors before the entry is considered disabled.
24    pub max_error_count: u64,
25    /// Current allocation count.
26    pub count: AtomicU64,
27    /// Current error count.
28    pub error_count: AtomicU64,
29    /// The actual value being balanced (e.g., client, resource).
30    pub value: T,
31}
32
33impl<T> Entry<T>
34where
35    T: Send + Sync + Clone + 'static,
36{
37    /// Reset both the allocation count and the error count.
38    pub fn reset(&self) {
39        self.count.store(0, Release);
40        self.error_count.store(0, Release);
41    }
42
43    /// Disable this entry by setting its error count to the maximum.
44    pub fn disable(&self) {
45        self.error_count.store(self.max_error_count, Release);
46    }
47}
48
49impl<T> Clone for Entry<T>
50where
51    T: Send + Sync + Clone + 'static,
52{
53    fn clone(&self) -> Self {
54        Self {
55            max_count: self.max_count.clone(),
56            max_error_count: self.max_error_count.clone(),
57            count: self.count.load(Acquire).into(),
58            error_count: self.error_count.load(Acquire).into(),
59            value: self.value.clone(),
60        }
61    }
62}
63
64/// Internal state for `FaultTolerant`.
65pub struct Inner<T>
66where
67    T: Send + Sync + Clone + 'static,
68{
69    /// List of entries to balance between.
70    pub entries: RwLock<Vec<Entry<T>>>,
71    /// Optional background timer handle for resetting counts periodically.
72    pub timer: Mutex<Option<JoinHandle<()>>>,
73    /// Interval duration for resetting counts.
74    pub interval: RwLock<Duration>,
75    /// The next scheduled reset time.
76    pub next_reset: RwLock<Instant>,
77}
78
79/// Fault-tolerant load balancer with per-entry rate limiting and error tracking.
80///
81/// Entries that exceed their error threshold are temporarily disabled
82/// until the next reset interval.
83#[derive(Clone)]
84pub struct FaultTolerant<T>
85where
86    T: Send + Sync + Clone + 'static,
87{
88    inner: Arc<Inner<T>>,
89}
90
91impl<T> FaultTolerant<T>
92where
93    T: Send + Sync + Clone + 'static,
94{
95    /// Create a new fault-tolerant load balancer with a fixed 1-second interval.
96    ///
97    /// # Arguments
98    ///
99    /// * `entries` - A vector of tuples `(max_count, max_error_count, value)`:
100    ///     - `max_count`: Maximum number of allocations allowed per interval.
101    ///     - `max_error_count`: Maximum number of errors allowed before disabling the entry.
102    ///     - `value`: value.
103    pub fn new(entries: Vec<(u64, u64, T)>) -> Self {
104        Self::new_interval(entries, Duration::from_secs(1))
105    }
106
107    /// Create a new fault-tolerant load balancer with a custom interval.
108    ///
109    /// # Arguments
110    ///
111    /// * `entries` - A vector of tuples `(max_count, max_error_count, value)`:
112    ///     - `max_count`: Maximum number of allocations allowed per interval.
113    ///     - `max_error_count`: Maximum number of errors allowed before disabling the entry.
114    ///     - `value`: value.
115    ///
116    /// * `interval` - Duration after which all allocation/error counts are reset.
117    pub fn new_interval(entries: Vec<(u64, u64, T)>, interval: Duration) -> Self {
118        Self {
119            inner: Arc::new(Inner {
120                entries: entries
121                    .into_iter()
122                    .map(|(max_count, max_error_count, value)| Entry {
123                        max_count,
124                        max_error_count,
125                        value,
126                        count: 0.into(),
127                        error_count: 0.into(),
128                    })
129                    .collect::<Vec<_>>()
130                    .into(),
131                timer: Mutex::new(None),
132                interval: interval.into(),
133                next_reset: RwLock::new(Instant::now() + interval),
134            }),
135        }
136    }
137
138    /// Execute a custom async update on the internal reference.
139    pub async fn update<F, R>(&self, handler: F) -> anyhow::Result<()>
140    where
141        F: Fn(Arc<Inner<T>>) -> R,
142        R: Future<Output = anyhow::Result<()>>,
143    {
144        handler(self.inner.clone()).await
145    }
146
147    /// Spawn a background task that calls `handler` every `interval`.
148    ///
149    /// The handler is called once immediately; if that initial call fails
150    /// the error is returned and no background task is spawned.
151    /// Returns a [`JoinHandle`] that can be aborted to stop the timer.
152    pub async fn update_timer<F, R>(
153        &self,
154        handler: F,
155        interval: Duration,
156    ) -> anyhow::Result<JoinHandle<()>>
157    where
158        F: Fn(Arc<Inner<T>>) -> R + Send + Sync + 'static,
159        R: Future<Output = anyhow::Result<()>> + Send,
160    {
161        handler(self.inner.clone()).await?;
162
163        let inner = self.inner.clone();
164
165        Ok(spawn(async move {
166            loop {
167                sleep(interval).await;
168                let _ = handler(inner.clone()).await;
169            }
170        }))
171    }
172
173    /// Allocate an entry, skipping the specified index if provided.
174    pub async fn alloc_skip(&self, skip_index: usize) -> (usize, T) {
175        loop {
176            if let Some(v) = self.try_alloc_skip(skip_index) {
177                return v;
178            }
179
180            let now = Instant::now();
181
182            let next = *self.inner.next_reset.read().await;
183
184            let remaining = if now < next {
185                next - now
186            } else {
187                Duration::ZERO
188            };
189
190            if remaining > Duration::ZERO {
191                sleep(remaining).await;
192            } else {
193                yield_now().await;
194            }
195        }
196    }
197
198    /// Try to allocate an entry immediately, skipping the specified index if provided.
199    pub fn try_alloc_skip(&self, skip_index: usize) -> Option<(usize, T)> {
200        if let Ok(mut timer_guard) = self.inner.timer.try_lock() {
201            if timer_guard.is_none() {
202                let this = self.inner.clone();
203
204                *timer_guard = Some(spawn(async move {
205                    let mut interval = *this.interval.read().await;
206
207                    *this.next_reset.write().await = Instant::now() + interval;
208
209                    loop {
210                        sleep(match this.interval.try_read() {
211                            Ok(v) => {
212                                interval = *v;
213                                interval
214                            }
215                            Err(_) => interval,
216                        })
217                        .await;
218
219                        let now = Instant::now();
220
221                        let entries = this.entries.read().await;
222
223                        for entry in entries.iter() {
224                            entry.count.store(0, Release);
225                        }
226
227                        *this.next_reset.write().await = now + interval;
228                    }
229                }));
230            }
231        }
232
233        if let Ok(entries) = self.inner.entries.try_read() {
234            let mut skip_count = 0;
235
236            for (i, entry) in entries.iter().enumerate() {
237                if i == skip_index {
238                    continue;
239                }
240
241                if entry.max_error_count != 0
242                    && entry.error_count.load(Acquire) >= entry.max_error_count
243                {
244                    skip_count += 1;
245                    continue;
246                }
247
248                let count = entry.count.load(Acquire);
249
250                if entry.max_count == 0
251                    || (count < entry.max_count
252                        && entry
253                            .count
254                            .compare_exchange(count, count + 1, Release, Acquire)
255                            .is_ok())
256                {
257                    return Some((i, entry.value.clone()));
258                }
259            }
260
261            if skip_count == entries.len() {
262                return None;
263            }
264        }
265
266        None
267    }
268
269    /// Record a successful operation for the entry at `index`.
270    ///
271    /// Decrements the error count by one, to a minimum of zero.
272    /// Use this after a request succeeds to gradually restore
273    /// a degraded entry's reputation.
274    pub fn success(&self, index: usize) {
275        if let Ok(entries) = self.inner.entries.try_read() {
276            if let Some(entry) = entries.get(index) {
277                let current = entry.error_count.load(Acquire);
278
279                if current != 0 {
280                    let _ =
281                        entry
282                            .error_count
283                            .compare_exchange(current, current - 1, Release, Acquire);
284                }
285            }
286        }
287    }
288
289    /// Record a failed operation for the entry at `index`.
290    ///
291    /// Increments the error count by one. When the error count reaches
292    /// `max_error_count`, the entry is temporarily disabled until the
293    /// next reset interval.
294    pub fn failure(&self, index: usize) {
295        if let Ok(entries) = self.inner.entries.try_read() {
296            if let Some(entry) = entries.get(index) {
297                entry.error_count.fetch_add(1, Release);
298            }
299        }
300    }
301}
302
303impl<T> LoadBalancer<T> for FaultTolerant<T>
304where
305    T: Clone + Send + Sync + 'static,
306{
307    /// Allocate an entry asynchronously.
308    fn alloc(&self) -> impl Future<Output = T> + Send {
309        async move { self.alloc_skip(usize::MAX).await.1 }
310    }
311
312    /// Attempt to allocate an entry immediately.
313    fn try_alloc(&self) -> Option<T> {
314        self.try_alloc_skip(usize::MAX).map(|v| v.1)
315    }
316}