governor/state/keyed.rs
1//! Keyed rate limiters (those that can hold one state per key).
2//!
3//! These are rate limiters that have one set of parameters (burst capacity per time period) but
4//! apply those to several sets of actual rate-limiting states, e.g. to enforce one API call rate
5//! limit per API key.
6//!
7//! Rate limiters based on these types are constructed with
8//! [the `RateLimiter` constructors](../struct.RateLimiter.html#keyed-rate-limiters---default-constructors)
9
10use core::hash::Hash;
11use core::num::NonZeroU32;
12use core::prelude::v1::*;
13
14use crate::state::StateStore;
15use crate::{
16 clock::{self, Reference},
17 errors::InsufficientCapacity,
18 middleware::RateLimitingMiddleware,
19 nanos::Nanos,
20 Quota, RateLimiter,
21};
22
23#[cfg(feature = "std")]
24pub type DefaultHasher = std::hash::RandomState;
25#[cfg(not(feature = "std"))]
26pub type DefaultHasher = hashbrown::DefaultHashBuilder;
27
28/// A trait for state stores with one rate limiting state per key.
29///
30/// This is blanket-implemented by all [`StateStore`]s with hashable (`Eq + Hash + Clone`) key
31/// associated types.
32pub trait KeyedStateStore<K: Hash>: StateStore<Key = K> {}
33
34impl<T, K: Hash> KeyedStateStore<K> for T
35where
36 T: StateStore<Key = K>,
37 K: Eq + Clone + Hash,
38{
39}
40
41/// # Keyed rate limiters - default constructors
42impl<K> RateLimiter<K, DefaultKeyedStateStore<K>, clock::DefaultClock>
43where
44 K: Clone + Hash + Eq,
45{
46 /// Constructs a new keyed rate limiter backed by
47 /// the [`DefaultKeyedStateStore`].
48 pub fn keyed(quota: Quota) -> Self {
49 let state = DefaultKeyedStateStore::default();
50 let clock = clock::DefaultClock::default();
51 RateLimiter::new(quota, state, clock)
52 }
53
54 #[cfg(all(feature = "std", feature = "dashmap"))]
55 /// Constructs a new keyed rate limiter explicitly backed by a [`DashMap`][::dashmap::DashMap].
56 pub fn dashmap(quota: Quota) -> Self {
57 let state = DashMapStateStore::default();
58 let clock = clock::DefaultClock::default();
59 RateLimiter::new(quota, state, clock)
60 }
61
62 #[cfg(any(all(feature = "std", not(feature = "dashmap")), not(feature = "std")))]
63 /// Constructs a new keyed rate limiter explicitly backed by a
64 /// [`HashMap`][std::collections::HashMap].
65 pub fn hashmap(quota: Quota) -> Self {
66 let state = HashMapStateStore::default();
67 let clock = clock::DefaultClock::default();
68 RateLimiter::new(quota, state, clock)
69 }
70}
71
72#[cfg(any(all(feature = "std", not(feature = "dashmap")), not(feature = "std")))]
73/// # Keyed rate limiters with custom hashers for std HashMap
74impl<K, S> RateLimiter<K, DefaultKeyedStateStore<K, S>, clock::DefaultClock>
75where
76 K: Clone + Hash + Eq,
77 S: core::hash::BuildHasher + Default,
78{
79 /// Constructs a new keyed rate limiter explicitly backed by a
80 /// [`HashMap`][hashmap::HashMap] with a custom hasher.
81 pub fn hashmap_with_hasher(quota: Quota, hasher: S) -> Self {
82 let state = HashMapStateStore::new(hashmap::HashMap::with_hasher(hasher));
83 let clock = clock::DefaultClock::default();
84 RateLimiter::new(quota, state, clock)
85 }
86}
87
88#[cfg(all(feature = "std", feature = "dashmap"))]
89/// # Keyed rate limiters with custom hashers
90impl<K, S> RateLimiter<K, DefaultKeyedStateStore<K, S>, clock::DefaultClock>
91where
92 K: Clone + Hash + Eq,
93 S: core::hash::BuildHasher + Clone + Default,
94{
95 /// Constructs a new keyed rate limiter explicitly backed by a
96 /// [`DashMap`][::dashmap::DashMap] with a custom hasher.
97 pub fn dashmap_with_hasher(quota: Quota, hasher: S) -> Self {
98 let state = DashMapStateStore::with_hasher(hasher);
99 let clock = clock::DefaultClock::default();
100 RateLimiter::new(quota, state, clock)
101 }
102}
103
104#[cfg(all(feature = "std", feature = "dashmap"))]
105impl<K> RateLimiter<K, HashMapStateStore<K>, clock::DefaultClock>
106where
107 K: Clone + Hash + Eq,
108{
109 /// Constructs a new keyed rate limiter explicitly backed by a
110 /// [`HashMap`][hashmap::HashMap].
111 pub fn hashmap(quota: Quota) -> Self {
112 let state = HashMapStateStore::default();
113 let clock = clock::DefaultClock::default();
114 RateLimiter::new(quota, state, clock)
115 }
116}
117
118#[cfg(all(feature = "std", feature = "dashmap"))]
119impl<K, S> RateLimiter<K, HashMapStateStore<K, S>, clock::DefaultClock>
120where
121 K: Clone + Hash + Eq,
122 S: core::hash::BuildHasher + Default + Clone,
123{
124 /// Constructs a new keyed rate limiter explicitly backed by a
125 /// [`HashMap`][hashmap::HashMap].
126 pub fn hashmap_with_hasher(quota: Quota, hasher: S) -> Self {
127 let state = HashMapStateStore::new(hashmap::HashMap::with_hasher(hasher));
128 let clock = clock::DefaultClock::default();
129 RateLimiter::new(quota, state, clock)
130 }
131}
132
133/// # Keyed rate limiters - Manually checking cells
134impl<K, S, C, MW> RateLimiter<K, S, C, MW>
135where
136 S: KeyedStateStore<K>,
137 K: Hash,
138 C: clock::Clock,
139 MW: RateLimitingMiddleware<C::Instant>,
140{
141 /// Allow a single cell through the rate limiter for the given key.
142 ///
143 /// If the rate limit is reached, `check_key` returns information about the earliest
144 /// time that a cell might be allowed through again under that key.
145 pub fn check_key(&self, key: &K) -> Result<MW::PositiveOutcome, MW::NegativeOutcome> {
146 self.gcra.test_and_update::<K, C::Instant, S, MW>(
147 self.start,
148 key,
149 &self.state,
150 self.clock.now(),
151 )
152 }
153
154 /// Allow *only all* `n` cells through the rate limiter for the given key.
155 ///
156 /// This method can succeed in only one way and fail in two ways:
157 /// * Success: If all `n` cells can be accommodated, it returns `Ok(Ok(()))`.
158 /// * Failure (but ok): Not all cells can make it through at the current time.
159 /// The result is `Ok(Err(NotUntil))`, which can
160 /// be interrogated about when the batch might next conform.
161 /// * Failure (the batch can never go through): The rate limit is too low for the given number
162 /// of cells. The result is `Err(InsufficientCapacity)`
163 ///
164 /// ### Performance
165 /// This method diverges a little from the GCRA algorithm, using
166 /// multiplication to determine the next theoretical arrival time, and so
167 /// is not as fast as checking a single cell.
168 pub fn check_key_n(
169 &self,
170 key: &K,
171 n: NonZeroU32,
172 ) -> Result<Result<MW::PositiveOutcome, MW::NegativeOutcome>, InsufficientCapacity> {
173 self.gcra.test_n_all_and_update::<K, C::Instant, S, MW>(
174 self.start,
175 key,
176 n,
177 &self.state,
178 self.clock.now(),
179 )
180 }
181}
182
183/// Keyed rate limiters that can be "cleaned up".
184///
185/// Any keyed state store implementing this trait allows users to evict elements that are
186/// indistinguishable from fresh rate-limiting states (that is, if a key hasn't been used for
187/// rate-limiting decisions for as long as the bucket capacity).
188///
189/// As this does not make sense for not all keyed state stores (e.g. stores that auto-expire like
190/// memcache), this is an optional trait. All the keyed state stores in this crate implement
191/// shrinking.
192pub trait ShrinkableKeyedStateStore<K: Hash>: KeyedStateStore<K> {
193 /// Remove those keys with state older than `drop_below`.
194 fn retain_recent(&self, drop_below: Nanos);
195
196 /// Shrinks the capacity of the state store, if possible.
197 ///
198 /// If the state store does not support shrinking, this method is a no-op.
199 fn shrink_to_fit(&self) {}
200
201 /// Returns the number of "live" keys stored in the state store.
202 ///
203 /// Depending on how the state store is implemented, this may
204 /// return an estimate or an out-of-date result.
205 fn len(&self) -> usize;
206
207 /// Returns `true` if `self` has no keys stored in it.
208 ///
209 /// As with [`len`](#tymethod.len), this method may return
210 /// imprecise results (indicating that the state store is empty
211 /// while a concurrent rate-limiting operation is taking place).
212 fn is_empty(&self) -> bool;
213}
214
215/// # Keyed rate limiters - Housekeeping
216///
217/// As the inputs to a keyed rate-limiter can be arbitrary keys, the set of retained keys retained
218/// grows, while the number of active keys may stay smaller. To save on space, a keyed rate-limiter
219/// allows removing those keys that are "stale", i.e., whose values are no different from keys' that
220/// aren't present in the rate limiter state store.
221impl<K, S, C, MW> RateLimiter<K, S, C, MW>
222where
223 S: ShrinkableKeyedStateStore<K>,
224 K: Hash,
225 C: clock::Clock,
226 MW: RateLimitingMiddleware<C::Instant>,
227{
228 /// Retains all keys in the rate limiter that were used recently enough.
229 ///
230 /// Any key whose rate limiting state is indistinguishable from a "fresh" state (i.e., the
231 /// theoretical arrival time lies in the past).
232 pub fn retain_recent(&self) {
233 // calculate the minimum retention parameter: Any key whose state store's theoretical
234 // arrival time is larger than a starting state for the bucket gets to stay, everything
235 // else (that's indistinguishable from a starting state) goes.
236 let now = self.clock.now();
237 let drop_below = now.duration_since(self.start).saturating_sub(self.gcra.t());
238
239 self.state.retain_recent(drop_below);
240 }
241
242 /// Shrinks the capacity of the rate limiter's state store, if possible.
243 pub fn shrink_to_fit(&self) {
244 self.state.shrink_to_fit();
245 }
246
247 /// Returns the number of "live" keys in the rate limiter's state store.
248 ///
249 /// Depending on how the state store is implemented, this may
250 /// return an estimate or an out-of-date result.
251 pub fn len(&self) -> usize {
252 self.state.len()
253 }
254
255 /// Returns `true` if the rate limiter has no keys in it.
256 ///
257 /// As with [`len`](#method.len), this method may return
258 /// imprecise results (indicating that the state store is empty
259 /// while a concurrent rate-limiting operation is taking place).
260 pub fn is_empty(&self) -> bool {
261 self.state.is_empty()
262 }
263}
264
265mod hashmap;
266
267pub use hashmap::HashMapStateStore;
268
269#[cfg(all(feature = "std", feature = "dashmap"))]
270mod dashmap;
271
272#[cfg(all(feature = "std", feature = "dashmap"))]
273pub use self::dashmap::DashMapStateStore;
274
275#[cfg(feature = "std")]
276mod future;
277
278#[cfg(any(all(feature = "std", not(feature = "dashmap")), not(feature = "std")))]
279/// The default keyed rate limiter type: a mutex-wrapped [`HashMap`][hashmap::Hashmap].
280pub type DefaultKeyedStateStore<K, S = DefaultHasher> = HashMapStateStore<K, S>;
281
282#[cfg(all(feature = "std", feature = "dashmap"))]
283/// The default keyed rate limiter type: the concurrent [`DashMap`][::dashmap::DashMap].
284pub type DefaultKeyedStateStore<K, S = DefaultHasher> = DashMapStateStore<K, S>;
285
286#[cfg(test)]
287mod test {
288 use core::marker::PhantomData;
289
290 use nonzero_ext::nonzero;
291
292 use crate::{
293 clock::{Clock, FakeRelativeClock},
294 middleware::NoOpMiddleware,
295 };
296
297 use super::*;
298
299 #[test]
300 fn default_nonshrinkable_state_store_coverage() {
301 #[derive(Default)]
302 struct NaiveKeyedStateStore<K>(PhantomData<K>);
303
304 impl<K: Hash + Eq + Clone> StateStore for NaiveKeyedStateStore<K> {
305 type Key = K;
306
307 fn measure_and_replace<T, F, E>(&self, _key: &Self::Key, f: F) -> Result<T, E>
308 where
309 F: Fn(Option<Nanos>) -> Result<(T, Nanos), E>,
310 {
311 f(None).map(|(res, _)| res)
312 }
313 }
314
315 impl<K: Hash + Eq + Clone> ShrinkableKeyedStateStore<K> for NaiveKeyedStateStore<K> {
316 fn retain_recent(&self, _drop_below: Nanos) {
317 // nothing to do
318 }
319
320 fn len(&self) -> usize {
321 0
322 }
323 fn is_empty(&self) -> bool {
324 true
325 }
326 }
327
328 let lim: RateLimiter<
329 u32,
330 NaiveKeyedStateStore<u32>,
331 FakeRelativeClock,
332 NoOpMiddleware<<FakeRelativeClock as Clock>::Instant>,
333 > = RateLimiter::new(
334 Quota::per_second(nonzero!(1_u32)),
335 NaiveKeyedStateStore::default(),
336 FakeRelativeClock::default(),
337 );
338 assert_eq!(lim.check_key(&1u32), Ok(()));
339 assert!(lim.is_empty());
340 assert_eq!(lim.len(), 0);
341 lim.retain_recent();
342 lim.shrink_to_fit();
343 }
344}