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}