load_balancer/rate_limit.rs
1use dashmap::DashMap;
2use dashmap::Entry;
3use std::hash::Hash;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6use tokio::sync::Mutex;
7use tokio::task::yield_now;
8use tokio::time::sleep;
9
10/// A token-bucket rate limiter.
11///
12/// Limits the rate of operations to `rate` per `interval`. When the
13/// rate is exceeded, further requests are denied until the next window.
14#[derive(Clone)]
15pub struct RateLimiter {
16 rate: u64,
17 interval: Duration,
18 state: Arc<Mutex<Inner>>,
19}
20
21struct Inner {
22 count: u64,
23 last_reset: Instant,
24}
25
26impl RateLimiter {
27 /// Create a new rate limiter allowing `rate` operations per second.
28 pub fn new_rate(rate: u64) -> Self {
29 Self::new_rate_interval(rate, Duration::from_secs(1))
30 }
31
32 /// Create a new rate limiter with a custom rate and interval.
33 pub fn new_rate_interval(rate: u64, interval: Duration) -> Self {
34 RateLimiter {
35 rate,
36 interval,
37 state: Arc::new(Mutex::new(Inner {
38 count: 0,
39 last_reset: Instant::now(),
40 })),
41 }
42 }
43
44 /// Check whether an operation is allowed right now.
45 ///
46 /// Returns `true` if within the rate limit, `false` if the bucket is exhausted.
47 pub async fn check(&self) -> bool {
48 let mut state = self.state.lock().await;
49 let now = Instant::now();
50
51 if now >= state.last_reset + self.interval {
52 state.count = 0;
53 state.last_reset = now;
54 }
55
56 if state.count < self.rate {
57 state.count += 1;
58 true
59 } else {
60 false
61 }
62 }
63
64 /// Returns the duration until the next token becomes available.
65 ///
66 /// Returns `Duration::ZERO` if tokens are immediately available,
67 /// `Duration::MAX` if the rate is zero.
68 pub async fn time_until_available(&self) -> Duration {
69 if self.rate == 0 {
70 return Duration::MAX;
71 }
72
73 let state = self.state.lock().await;
74 let now = Instant::now();
75 let window_end = state.last_reset + self.interval;
76
77 if now >= window_end || state.count < self.rate {
78 Duration::ZERO
79 } else {
80 window_end - now
81 }
82 }
83
84 /// Block until a token is available, then consume it.
85 ///
86 /// Sleeps for the remaining window duration when the bucket is exhausted,
87 /// then retries. Returns immediately if tokens are available.
88 pub async fn wait(&self) {
89 loop {
90 let mut state = self.state.lock().await;
91 let now = Instant::now();
92
93 if now >= state.last_reset + self.interval {
94 state.count = 0;
95 state.last_reset = now;
96 }
97
98 if state.count < self.rate {
99 state.count += 1;
100 return;
101 }
102
103 let wait = (state.last_reset + self.interval).saturating_duration_since(now);
104
105 drop(state);
106
107 if wait > Duration::ZERO {
108 sleep(wait).await;
109 } else {
110 yield_now().await;
111 }
112 }
113 }
114}
115
116/// A per-key rate limiter map.
117///
118/// Each key can be assigned its own rate and interval via [`insert_rate`](Self::insert_rate).
119/// Keys that have not been inserted are rejected by default — call
120/// [`insert_rate`](Self::insert_rate) or [`insert_rate_interval`](Self::insert_rate_interval) first.
121///
122/// # Example
123///
124/// ```rust
125/// use load_balancer::rate_limit::RateLimiterMap;
126/// use std::time::Duration;
127///
128/// #[tokio::main]
129/// async fn main() {
130/// let map = RateLimiterMap::new();
131///
132/// // alice: 3 req/s, bob: 10 req/s
133/// map.insert_rate("alice", 3);
134/// map.insert_rate_interval("bob", 10, Duration::from_secs(1));
135///
136/// assert!(map.check(&"alice").await);
137/// assert!(map.check(&"bob").await);
138/// assert!(!map.check(&"charlie").await); // not inserted → rejected
139/// }
140/// ```
141#[derive(Clone)]
142pub struct RateLimiterMap<K> {
143 map: Arc<DashMap<K, RateLimiter>>,
144}
145
146impl<K> RateLimiterMap<K>
147where
148 K: Hash + Eq + Clone + Send + Sync + 'static,
149{
150 /// Create an empty map.
151 pub fn new() -> Self {
152 RateLimiterMap {
153 map: Arc::new(DashMap::new()),
154 }
155 }
156
157 /// Insert a rate limiter for `key` with the given `rate` and a 1-second interval.
158 ///
159 /// Overwrites any existing limiter for the same key.
160 pub fn insert_rate(&self, key: K, rate: u64) {
161 self.insert_rate_interval(key, rate, Duration::from_secs(1))
162 }
163
164 /// Insert a rate limiter for `key` with the given `rate` and `interval`.
165 pub fn insert_rate_interval(&self, key: K, rate: u64, interval: Duration) {
166 let limiter = RateLimiter::new_rate_interval(rate, interval);
167 self.map.insert(key, limiter);
168 }
169
170 /// Remove the rate limiter for `key`.
171 ///
172 /// After removal the key is unknown and will be rejected by
173 /// [`check`](Self::check) (same as a key that was never inserted).
174 pub fn remove(&self, key: &K) {
175 self.map.remove(key);
176 }
177
178 /// Returns the number of keys currently managed.
179 pub fn len(&self) -> usize {
180 self.map.len()
181 }
182
183 /// Returns `true` if no keys are being rate-limited.
184 pub fn is_empty(&self) -> bool {
185 self.map.is_empty()
186 }
187
188 /// Returns `true` if `key` has an active rate limiter.
189 pub fn contains_key(&self, key: &K) -> bool {
190 self.map.contains_key(key)
191 }
192
193 /// Remove all rate limiters.
194 pub fn clear(&self) {
195 self.map.clear();
196 }
197
198 /// Retain only the keys for which `f` returns `true`.
199 ///
200 /// Each remaining key's limiter is unchanged.
201 pub fn retain(&self, f: impl FnMut(&K, &mut RateLimiter) -> bool) {
202 self.map.retain(f);
203 }
204
205 /// Get an [`Entry`] for `key`, allowing in-place insertion or modification.
206 ///
207 /// Use `entry(key).or_insert(limiter)` to insert a limiter only if the key
208 /// is absent, or `entry(key).and_modify(...)` to adjust an existing one.
209 pub fn entry(&self, key: K) -> Entry<'_, K, RateLimiter> {
210 self.map.entry(key)
211 }
212
213 /// Check whether `key` is allowed right now.
214 ///
215 /// Returns `true` if the key's limiter has available tokens.
216 /// Returns `false` if the key has not been inserted (unknown keys are
217 /// rejected by default).
218 pub async fn check(&self, key: &K) -> bool {
219 match self.map.get(key).map(|r| r.clone()) {
220 Some(limiter) => limiter.check().await,
221 None => false,
222 }
223 }
224
225 /// Check `key` and distinguish "unknown" from "rate-limited".
226 ///
227 /// Returns `Some(true)` if allowed, `Some(false)` if rate-limited,
228 /// `None` if the key has not been inserted.
229 pub async fn check_opt(&self, key: &K) -> Option<bool> {
230 match self.map.get(key).map(|r| r.clone()) {
231 Some(limiter) => Some(limiter.check().await),
232 None => None,
233 }
234 }
235
236 /// Returns the duration until `key` becomes available again.
237 ///
238 /// Returns `Duration::MAX` if the key is not inserted or the rate is zero,
239 /// `Duration::ZERO` if tokens are immediately available.
240 pub async fn time_until_available(&self, key: &K) -> Duration {
241 match self.map.get(key).map(|r| r.clone()) {
242 Some(limiter) => limiter.time_until_available().await,
243 None => Duration::MAX,
244 }
245 }
246
247 /// Block until `key` is allowed, then consume a token.
248 ///
249 /// Returns immediately if the key is not inserted (no-op).
250 pub async fn wait(&self, key: &K) {
251 if let Some(limiter) = self.map.get(key).map(|r| r.clone()) {
252 limiter.wait().await;
253 }
254 }
255}