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