Skip to main content

load_balancer/
window.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/// A single entry in the `Window` load balancer.
16///
17/// Tracks the maximum number of allowed allocations within the interval
18/// and the current allocation count.
19pub struct Entry<T>
20where
21    T: Send + Sync + Clone + 'static,
22{
23    /// Maximum number of allocations allowed for this entry in the interval.
24    pub max_count: u64,
25    /// Current allocation count within the interval.
26    pub count: AtomicU64,
27    /// The underlying value/resource of type `T`.
28    pub value: T,
29}
30
31impl<T> Clone for Entry<T>
32where
33    T: Send + Sync + Clone + 'static,
34{
35    fn clone(&self) -> Self {
36        Self {
37            max_count: self.max_count.clone(),
38            count: self.count.load(Acquire).into(),
39            value: self.value.clone(),
40        }
41    }
42}
43
44/// Internal state for `Window`.
45///
46/// Holds the entries and the interval timer.
47pub struct Inner<T>
48where
49    T: Send + Sync + Clone + 'static,
50{
51    /// The entries managed by this load balancer.
52    pub entries: RwLock<Vec<Entry<T>>>,
53    /// Timer task handle for resetting counts periodically.
54    pub timer: Mutex<Option<JoinHandle<()>>>,
55    /// The interval at which counts are reset.
56    pub interval: RwLock<Duration>,
57    /// The next scheduled reset time.
58    pub next_reset: RwLock<Instant>,
59}
60
61/// A load balancer that limits the number of allocations per entry
62/// over a specified time interval.
63///
64/// This implementation supports both async and sync allocation.
65#[derive(Clone)]
66pub struct Window<T>
67where
68    T: Send + Sync + Clone + 'static,
69{
70    /// Shared reference to the internal state.
71    inner: Arc<Inner<T>>,
72}
73
74impl<T> Window<T>
75where
76    T: Send + Sync + Clone + 'static,
77{
78    /// Create a new `Window` with the default interval of 1 second.
79    ///
80    /// # Arguments
81    ///
82    /// * `entries` - A vector of tuples `(max_count, value)`.
83    pub fn new(entries: Vec<(u64, T)>) -> Self {
84        Self::new_interval(entries, Duration::from_secs(1))
85    }
86
87    /// Create a new `Window` with a custom interval duration.
88    ///
89    /// # Arguments
90    ///
91    /// * `entries` - A vector of tuples `(max_count, value)`.
92    /// * `interval` - Duration after which allocation counts are reset.
93    pub fn new_interval(entries: Vec<(u64, T)>, interval: Duration) -> Self {
94        Self {
95            inner: Arc::new(Inner {
96                entries: entries
97                    .into_iter()
98                    .map(|(max_count, value)| Entry {
99                        max_count,
100                        value,
101                        count: 0.into(),
102                    })
103                    .collect::<Vec<_>>()
104                    .into(),
105                timer: Mutex::new(None),
106                interval: interval.into(),
107                next_reset: RwLock::new(Instant::now() + interval),
108            }),
109        }
110    }
111
112    /// Update the load balancer using an async callback.
113    pub async fn update<F, R, N>(&self, handler: F) -> anyhow::Result<N>
114    where
115        F: Fn(Arc<Inner<T>>) -> R,
116        R: Future<Output = anyhow::Result<N>>,
117    {
118        handler(self.inner.clone()).await
119    }
120
121    /// Spawn a background task that calls `handler` every `interval`.
122    ///
123    /// The handler is called once immediately; if that initial call fails
124    /// the error is returned and no background task is spawned.
125    /// Returns a [`JoinHandle`] that can be aborted to stop the timer.
126    pub async fn update_timer<F, R>(
127        &self,
128        handler: F,
129        interval: Duration,
130    ) -> anyhow::Result<JoinHandle<()>>
131    where
132        F: Fn(Arc<Inner<T>>) -> R + Send + Sync + 'static,
133        R: Future<Output = anyhow::Result<()>> + Send,
134    {
135        handler(self.inner.clone()).await?;
136
137        let inner = self.inner.clone();
138
139        Ok(spawn(async move {
140            loop {
141                sleep(interval).await;
142                let _ = handler(inner.clone()).await;
143            }
144        }))
145    }
146
147    /// Asynchronously allocate an entry, skipping the specified index.
148    /// Loops until a valid entry is found.
149    pub async fn alloc_skip(&self, index: usize) -> (usize, T) {
150        loop {
151            if let Some(v) = self.try_alloc_skip(index) {
152                return v;
153            }
154
155            let now = Instant::now();
156
157            let next = *self.inner.next_reset.read().await;
158
159            let remaining = if now < next {
160                next - now
161            } else {
162                Duration::ZERO
163            };
164
165            if remaining > Duration::ZERO {
166                sleep(remaining).await;
167            } else {
168                yield_now().await;
169            }
170        }
171    }
172
173    /// Try to allocate an entry without awaiting.
174    /// Returns `None` immediately if no entry is available.
175    pub fn try_alloc_skip(&self, index: usize) -> Option<(usize, T)> {
176        if let Ok(mut timer_guard) = self.inner.timer.try_lock() {
177            if timer_guard.is_none() {
178                let this = self.inner.clone();
179
180                *timer_guard = Some(spawn(async move {
181                    let mut interval = *this.interval.read().await;
182
183                    *this.next_reset.write().await = Instant::now() + interval;
184
185                    loop {
186                        sleep(match this.interval.try_read() {
187                            Ok(v) => {
188                                interval = *v;
189                                interval
190                            }
191                            Err(_) => interval,
192                        })
193                        .await;
194
195                        let now = Instant::now();
196
197                        let entries = this.entries.read().await;
198
199                        for entry in entries.iter() {
200                            entry.count.store(0, Release);
201                        }
202
203                        *this.next_reset.write().await = now + interval;
204                    }
205                }));
206            }
207        }
208
209        if let Ok(entries) = self.inner.entries.try_read() {
210            for (i, n) in entries.iter().enumerate() {
211                if i == index {
212                    continue;
213                }
214
215                let count = n.count.load(Acquire);
216
217                if n.max_count == 0
218                    || count < n.max_count
219                        && n.count
220                            .compare_exchange(count, count + 1, Release, Acquire)
221                            .is_ok()
222                {
223                    return Some((i, n.value.clone()));
224                }
225            }
226        }
227
228        None
229    }
230}
231
232impl<T> LoadBalancer<T> for Window<T>
233where
234    T: Send + Sync + Clone + 'static,
235{
236    /// Asynchronously allocate a resource from the load balancer.
237    fn alloc(&self) -> impl Future<Output = T> + Send {
238        async move { self.alloc_skip(usize::MAX).await.1 }
239    }
240
241    /// Synchronously try to allocate a resource.
242    fn try_alloc(&self) -> Option<T> {
243        self.try_alloc_skip(usize::MAX).map(|v| v.1)
244    }
245}