Skip to main content

load_balancer/
window.rs

1use crate::FilteredLoadBalancer;
2use crate::LoadBalancer;
3use std::sync::atomic::Ordering::{Acquire, Release};
4use std::{
5    future::Future,
6    sync::{Arc, atomic::AtomicU64},
7    time::{Duration, Instant},
8};
9use tokio::{
10    spawn,
11    sync::{Mutex, RwLock},
12    task::{JoinHandle, yield_now},
13    time::sleep,
14};
15
16/// Per-entry state for the sliding-window limiter (`max_count == 0` means unlimited).
17pub struct Entry<T>
18where
19    T: Send + Sync + Clone + 'static,
20{
21    pub max_count: u64,
22    pub count: AtomicU64,
23    pub value: T,
24}
25
26impl<T> Clone for Entry<T>
27where
28    T: Send + Sync + Clone + 'static,
29{
30    fn clone(&self) -> Self {
31        Self {
32            max_count: self.max_count.clone(),
33            count: self.count.load(Acquire).into(),
34            value: self.value.clone(),
35        }
36    }
37}
38
39pub struct Inner<T>
40where
41    T: Send + Sync + Clone + 'static,
42{
43    pub entries: RwLock<Vec<Entry<T>>>,
44    pub timer: Mutex<Option<JoinHandle<()>>>,
45    pub interval: RwLock<Duration>,
46    pub next_reset: RwLock<Instant>,
47}
48
49/// Sliding-window load balancer: each entry has a per-interval allocation cap.
50///
51/// Counts reset every `interval` (default 1 second); entries with `max_count == 0`
52/// are unlimited.
53#[derive(Clone)]
54pub struct Window<T>
55where
56    T: Send + Sync + Clone + 'static,
57{
58    inner: Arc<Inner<T>>,
59}
60
61impl<T> Window<T>
62where
63    T: Send + Sync + Clone + 'static,
64{
65    /// Create a `Window` with a 1-second reset interval.
66    ///
67    /// Each tuple is `(max_count, value)` — `max_count == 0` means unlimited.
68    pub fn new(entries: Vec<(u64, T)>) -> Self {
69        Self::new_interval(entries, Duration::from_secs(1))
70    }
71
72    /// Create a `Window` with a custom reset `interval`.
73    pub fn new_interval(entries: Vec<(u64, T)>, interval: Duration) -> Self {
74        Self {
75            inner: Arc::new(Inner {
76                entries: entries
77                    .into_iter()
78                    .map(|(max_count, value)| Entry {
79                        max_count,
80                        value,
81                        count: 0.into(),
82                    })
83                    .collect::<Vec<_>>()
84                    .into(),
85                timer: Mutex::new(None),
86                interval: interval.into(),
87                next_reset: RwLock::new(Instant::now() + interval),
88            }),
89        }
90    }
91
92    /// Run an async callback with access to the internal state.
93    pub async fn update<F, R, N>(&self, handler: F) -> anyhow::Result<N>
94    where
95        F: Fn(Arc<Inner<T>>) -> R,
96        R: Future<Output = anyhow::Result<N>>,
97    {
98        handler(self.inner.clone()).await
99    }
100
101    /// Spawn a background task that calls `handler` every `interval`.
102    ///
103    /// The handler is called once immediately; if that initial call fails
104    /// the error is returned and no background task is spawned.
105    /// Returns a [`JoinHandle`] that can be aborted to stop the timer.
106    pub async fn update_timer<F, R>(
107        &self,
108        handler: F,
109        interval: Duration,
110    ) -> anyhow::Result<JoinHandle<()>>
111    where
112        F: Fn(Arc<Inner<T>>) -> R + Send + Sync + 'static,
113        R: Future<Output = anyhow::Result<()>> + Send,
114    {
115        handler(self.inner.clone()).await?;
116
117        let inner = self.inner.clone();
118
119        Ok(spawn(async move {
120            loop {
121                sleep(interval).await;
122                let _ = handler(inner.clone()).await;
123            }
124        }))
125    }
126}
127
128impl<T> FilteredLoadBalancer<T> for Window<T>
129where
130    T: Send + Sync + Clone + 'static,
131{
132    async fn alloc_filter(
133        &self,
134        mut predicate: impl FnMut((usize, &T)) -> bool + Send,
135    ) -> (usize, T) {
136        loop {
137            if let Some(result) = self.try_alloc_filter(&mut predicate) {
138                return result;
139            }
140
141            let now = Instant::now();
142
143            let next = *self.inner.next_reset.read().await;
144
145            let remaining = if now < next {
146                next - now
147            } else {
148                Duration::ZERO
149            };
150
151            if remaining > Duration::ZERO {
152                sleep(remaining).await;
153            } else {
154                yield_now().await;
155            }
156        }
157    }
158
159    fn try_alloc_filter(
160        &self,
161        mut predicate: impl FnMut((usize, &T)) -> bool,
162    ) -> Option<(usize, T)> {
163        if let Ok(mut timer_guard) = self.inner.timer.try_lock() {
164            if timer_guard.is_none() {
165                let this = self.inner.clone();
166
167                *timer_guard = Some(spawn(async move {
168                    let mut interval = *this.interval.read().await;
169
170                    *this.next_reset.write().await = Instant::now() + interval;
171
172                    loop {
173                        sleep(match this.interval.try_read() {
174                            Ok(v) => {
175                                interval = *v;
176                                interval
177                            }
178                            Err(_) => interval,
179                        })
180                        .await;
181
182                        let now = Instant::now();
183
184                        let entries = this.entries.read().await;
185
186                        for entry in entries.iter() {
187                            entry.count.store(0, Release);
188                        }
189
190                        *this.next_reset.write().await = now + interval;
191                    }
192                }));
193            }
194        }
195
196        if let Ok(entries) = self.inner.entries.try_read() {
197            for (i, entry) in entries.iter().enumerate() {
198                if !predicate((i, &entry.value)) {
199                    continue;
200                }
201
202                let count = entry.count.load(Acquire);
203
204                if entry.max_count == 0
205                    || count < entry.max_count
206                        && entry
207                            .count
208                            .compare_exchange(count, count + 1, Release, Acquire)
209                            .is_ok()
210                {
211                    return Some((i, entry.value.clone()));
212                }
213            }
214        }
215
216        None
217    }
218}
219
220impl<T> LoadBalancer<T> for Window<T>
221where
222    T: Send + Sync + Clone + 'static,
223{
224    fn alloc(&self) -> impl Future<Output = T> + Send {
225        async move { self.alloc_filter(|_| true).await.1 }
226    }
227
228    fn try_alloc(&self) -> Option<T> {
229        self.try_alloc_filter(|_| true).map(|v| v.1)
230    }
231}