Skip to main content

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}