ratelimit_meter/state/direct.rs
1//! An in-memory rate limiter that can make decisions for a single
2//! situation.
3
4use crate::lib::*;
5
6use crate::{
7 algorithms::{Algorithm, DefaultAlgorithm},
8 clock, InconsistentCapacity, NegativeMultiDecision,
9};
10
11/// An in-memory rate limiter that makes direct (un-keyed)
12/// rate-limiting decisions. Direct rate limiters can be used to
13/// e.g. regulate the transmission of packets on a single connection,
14/// or to ensure that an API client stays within a server's rate
15/// limit.
16#[derive(Debug, Clone)]
17pub struct DirectRateLimiter<
18 A: Algorithm<C::Instant> = DefaultAlgorithm,
19 C: clock::Clock = clock::DefaultClock,
20> {
21 state: A::BucketState,
22 algorithm: A,
23 clock: C,
24}
25
26impl<A, C> DirectRateLimiter<A, C>
27where
28 C: clock::Clock,
29 A: Algorithm<C::Instant>,
30{
31 /// Construct a new rate limiter that allows `capacity` cells per
32 /// time unit through.
33 /// # Examples
34 /// You can construct a GCRA rate limiter like so:
35 /// ```
36 /// # use std::num::NonZeroU32;
37 /// # use std::time::Duration;
38 /// use ratelimit_meter::{DirectRateLimiter, GCRA};
39 /// # #[macro_use] extern crate nonzero_ext;
40 /// # extern crate ratelimit_meter;
41 /// # fn main () {
42 /// let _gcra = DirectRateLimiter::<GCRA>::new(nonzero!(100u32), Duration::from_secs(5));
43 /// # }
44 /// ```
45 ///
46 /// and similarly, for a leaky bucket:
47 /// ```
48 /// # use std::time::Duration;
49 /// use ratelimit_meter::{DirectRateLimiter, LeakyBucket};
50 /// # #[macro_use] extern crate nonzero_ext;
51 /// # extern crate ratelimit_meter;
52 /// # fn main () {
53 /// let _lb = DirectRateLimiter::<LeakyBucket>::new(nonzero!(100u32), Duration::from_secs(5));
54 /// # }
55 /// ```
56 pub fn new(capacity: NonZeroU32, per_time_unit: Duration) -> Self {
57 DirectRateLimiter {
58 state: <A as Algorithm<C::Instant>>::BucketState::default(),
59 algorithm: <A as Algorithm<C::Instant>>::construct(
60 capacity,
61 nonzero!(1u32),
62 per_time_unit,
63 )
64 .unwrap(),
65 clock: Default::default(),
66 }
67 }
68
69 /// Construct a new rate limiter that allows `capacity` cells per
70 /// second.
71 /// # Examples
72 /// Constructing a GCRA rate limiter that lets through 100 cells per second:
73 /// ```
74 /// # use std::time::Duration;
75 /// use ratelimit_meter::{DirectRateLimiter, GCRA};
76 /// # #[macro_use] extern crate nonzero_ext;
77 /// # extern crate ratelimit_meter;
78 /// # fn main () {
79 /// let _gcra = DirectRateLimiter::<GCRA>::per_second(nonzero!(100u32));
80 /// # }
81 /// ```
82 ///
83 /// and a leaky bucket:
84 /// ```
85 /// # use std::time::Duration;
86 /// use ratelimit_meter::{DirectRateLimiter, LeakyBucket};
87 /// # #[macro_use] extern crate nonzero_ext;
88 /// # extern crate ratelimit_meter;
89 /// # fn main () {
90 /// let _gcra = DirectRateLimiter::<LeakyBucket>::per_second(nonzero!(100u32));
91 /// # }
92 /// ```
93 pub fn per_second(capacity: NonZeroU32) -> Self {
94 Self::new(capacity, Duration::from_secs(1))
95 }
96
97 /// Return a builder that can be used to construct a rate limiter using
98 /// the parameters passed to the Builder.
99 pub fn build_with_capacity(capacity: NonZeroU32) -> Builder<C, A> {
100 Builder {
101 capacity,
102 cell_weight: nonzero!(1u32),
103 time_unit: Duration::from_secs(1),
104 end_result: PhantomData,
105 clock: Default::default(),
106 }
107 }
108
109 /// Tests whether a single cell can be accommodated at the given
110 /// time stamp. See [`check`](#method.check).
111 pub fn check_at(
112 &mut self,
113 at: C::Instant,
114 ) -> Result<(), <A as Algorithm<C::Instant>>::NegativeDecision> {
115 self.algorithm.test_and_update(&self.state, at)
116 }
117
118 /// Tests if `n` cells can be accommodated at the given time
119 /// (`Instant::now()`), using [`check_n`](#method.check_n)
120 pub fn check_n_at(
121 &mut self,
122 n: u32,
123 at: C::Instant,
124 ) -> Result<(), NegativeMultiDecision<<A as Algorithm<C::Instant>>::NegativeDecision>> {
125 self.algorithm.test_n_and_update(&self.state, n, at)
126 }
127
128 /// Tests if a single cell can be accommodated at the clock's
129 /// current reading. If it can be, `check` updates the rate
130 /// limiter state to account for the conforming cell and returns
131 /// `Ok(())`.
132 ///
133 /// If the cell is non-conforming (i.e., it can't be accomodated
134 /// at this time stamp), `check_at` returns `Err` with information
135 /// about the earliest time at which a cell could be considered
136 /// conforming.
137 pub fn check(&mut self) -> Result<(), <A as Algorithm<C::Instant>>::NegativeDecision> {
138 self.algorithm
139 .test_and_update(&self.state, self.clock.now())
140 }
141
142 /// Tests if `n` cells can be accommodated at the clock's current
143 /// reading. If (and only if) all cells in the batch can be
144 /// accomodated, the `MultiDecider` updates the internal state to
145 /// account for all cells and returns `Ok(())`.
146 ///
147 /// If the entire batch of cells would not be conforming but the
148 /// rate limiter has the capacity to accomodate the cells at any
149 /// point in time, `check_n_at` returns error
150 /// [`NegativeMultiDecision::BatchNonConforming`](../../enum.NegativeMultiDecision.html#variant.BatchNonConforming),
151 /// holding the number of cells the rate limiter's negative
152 /// outcome result.
153 ///
154 /// If `n` exceeds the bucket capacity, `check_n_at` returns
155 /// [`NegativeMultiDecision::InsufficientCapacity`](../../enum.NegativeMultiDecision.html#variant.InsufficientCapacity),
156 /// indicating that a batch of this many cells can never succeed.
157 pub fn check_n(
158 &mut self,
159 n: u32,
160 ) -> Result<(), NegativeMultiDecision<<A as Algorithm<C::Instant>>::NegativeDecision>> {
161 self.algorithm
162 .test_n_and_update(&self.state, n, self.clock.now())
163 }
164}
165
166/// An object that allows incrementally constructing rate Limiter
167/// objects.
168pub struct Builder<C, A>
169where
170 C: clock::Clock,
171 A: Algorithm<C::Instant> + Sized,
172{
173 capacity: NonZeroU32,
174 cell_weight: NonZeroU32,
175 time_unit: Duration,
176 end_result: PhantomData<A>,
177 clock: C,
178}
179
180impl<C, A> Builder<C, A>
181where
182 C: clock::Clock,
183 A: Algorithm<C::Instant> + Sized,
184{
185 /// Sets the "weight" of each cell being checked against the
186 /// bucket. Each cell fills the bucket by this much.
187 pub fn cell_weight(
188 &mut self,
189 weight: NonZeroU32,
190 ) -> Result<&mut Builder<C, A>, InconsistentCapacity> {
191 if self.cell_weight > self.capacity {
192 return Err(InconsistentCapacity::new(self.capacity, self.cell_weight));
193 }
194 self.cell_weight = weight;
195 Ok(self)
196 }
197
198 /// Sets the "unit of time" within which the bucket drains.
199 ///
200 /// The assumption is that in a period of `time_unit` (if no cells
201 /// are being checked), the bucket is fully drained.
202 pub fn per(&mut self, time_unit: Duration) -> &mut Builder<C, A> {
203 self.time_unit = time_unit;
204 self
205 }
206
207 /// Sets the clock used by the bucket.
208 pub fn using_clock(&mut self, clock: C) -> &mut Builder<C, A> {
209 self.clock = clock;
210 self
211 }
212
213 /// Builds a rate limiter of the specified type.
214 pub fn build(&self) -> Result<DirectRateLimiter<A, C>, InconsistentCapacity> {
215 Ok(DirectRateLimiter {
216 state: <A as Algorithm<C::Instant>>::BucketState::default(),
217 algorithm: <A as Algorithm<C::Instant>>::construct(
218 self.capacity,
219 self.cell_weight,
220 self.time_unit,
221 )?,
222 clock: self.clock.clone(),
223 })
224 }
225}