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