nautilus_network/ratelimiter/clock.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Time sources for rate limiters.
17//!
18//! The time sources contained in this module allow the rate limiter
19//! to be (optionally) independent of std, and additionally
20//! allow mocking the passage of time.
21//!
22//! You can supply a custom time source by implementing both [`Reference`]
23//! and [`Clock`] for your own types, and by implementing `Add<Nanos>` for
24//! your [`Reference`] type:
25use std::{
26 fmt::Debug,
27 ops::Add,
28 prelude::v1::*,
29 sync::{
30 atomic::{AtomicU64, Ordering},
31 Arc,
32 },
33 time::{Duration, Instant},
34};
35
36use super::nanos::Nanos;
37
38/// A measurement from a clock.
39pub trait Reference:
40 Sized + Add<Nanos, Output = Self> + PartialEq + Eq + Ord + Copy + Clone + Send + Sync + Debug
41{
42 /// Determines the time that separates two measurements of a
43 /// clock. Implementations of this must perform a saturating
44 /// subtraction - if the `earlier` timestamp should be later,
45 /// `duration_since` must return the zero duration.
46 fn duration_since(&self, earlier: Self) -> Nanos;
47
48 /// Returns a reference point that lies at most `duration` in the
49 /// past from the current reference. If an underflow should occur,
50 /// returns the current reference.
51 fn saturating_sub(&self, duration: Nanos) -> Self;
52}
53
54/// A time source used by rate limiters.
55pub trait Clock: Clone {
56 /// A measurement of a monotonically increasing clock.
57 type Instant: Reference;
58
59 /// Returns a measurement of the clock.
60 fn now(&self) -> Self::Instant;
61}
62
63impl Reference for Duration {
64 /// The internal duration between this point and another.
65 fn duration_since(&self, earlier: Self) -> Nanos {
66 self.checked_sub(earlier)
67 .unwrap_or_else(|| Self::new(0, 0))
68 .into()
69 }
70
71 /// The internal duration between this point and another.
72 fn saturating_sub(&self, duration: Nanos) -> Self {
73 self.checked_sub(duration.into()).unwrap_or(*self)
74 }
75}
76
77impl Add<Nanos> for Duration {
78 type Output = Self;
79
80 fn add(self, other: Nanos) -> Self {
81 let other: Self = other.into();
82 self + other
83 }
84}
85
86/// A mock implementation of a clock. All it does is keep track of
87/// what "now" is (relative to some point meaningful to the program),
88/// and returns that.
89///
90/// # Thread safety
91/// The mock time is represented as an atomic u64 count of nanoseconds, behind an [`Arc`].
92/// Clones of this clock will all show the same time, even if the original advances.
93#[derive(Debug, Clone, Default)]
94pub struct FakeRelativeClock {
95 now: Arc<AtomicU64>,
96}
97
98impl FakeRelativeClock {
99 /// Advances the fake clock by the given amount.
100 pub fn advance(&self, by: Duration) {
101 let by: u64 = by
102 .as_nanos()
103 .try_into()
104 .expect("Can not represent times past ~584 years");
105
106 let mut prev = self.now.load(Ordering::Acquire);
107 let mut next = prev + by;
108 while let Err(next_prev) =
109 self.now
110 .compare_exchange_weak(prev, next, Ordering::Release, Ordering::Relaxed)
111 {
112 prev = next_prev;
113 next = prev + by;
114 }
115 }
116}
117
118impl PartialEq for FakeRelativeClock {
119 fn eq(&self, other: &Self) -> bool {
120 self.now.load(Ordering::Relaxed) == other.now.load(Ordering::Relaxed)
121 }
122}
123
124impl Clock for FakeRelativeClock {
125 type Instant = Nanos;
126
127 fn now(&self) -> Self::Instant {
128 self.now.load(Ordering::Relaxed).into()
129 }
130}
131
132/// The monotonic clock implemented by [`Instant`].
133#[derive(Clone, Debug, Default)]
134pub struct MonotonicClock;
135
136impl Add<Nanos> for Instant {
137 type Output = Self;
138
139 fn add(self, other: Nanos) -> Self {
140 let other: Duration = other.into();
141 self + other
142 }
143}
144
145impl Reference for Instant {
146 fn duration_since(&self, earlier: Self) -> Nanos {
147 if earlier < *self {
148 (*self - earlier).into()
149 } else {
150 Nanos::from(Duration::new(0, 0))
151 }
152 }
153
154 fn saturating_sub(&self, duration: Nanos) -> Self {
155 self.checked_sub(duration.into()).unwrap_or(*self)
156 }
157}
158
159impl Clock for MonotonicClock {
160 type Instant = Instant;
161
162 fn now(&self) -> Self::Instant {
163 Instant::now()
164 }
165}
166
167////////////////////////////////////////////////////////////////////////////////
168// Tests
169////////////////////////////////////////////////////////////////////////////////
170#[cfg(test)]
171mod test {
172 use std::{iter::repeat, sync::Arc, thread, time::Duration};
173
174 use super::*;
175
176 #[test]
177 fn fake_clock_parallel_advances() {
178 let clock = Arc::new(FakeRelativeClock::default());
179 let threads = repeat(())
180 .take(10)
181 .map(move |()| {
182 let clock = Arc::clone(&clock);
183 thread::spawn(move || {
184 for _ in 0..1_000_000 {
185 let now = clock.now();
186 clock.advance(Duration::from_nanos(1));
187 assert!(clock.now() > now);
188 }
189 })
190 })
191 .collect::<Vec<_>>();
192 for t in threads {
193 t.join().unwrap();
194 }
195 }
196
197 #[test]
198 fn duration_addition_coverage() {
199 let d = Duration::from_secs(1);
200 let one_ns = Nanos::from(1);
201 assert!(d + one_ns > d);
202 }
203}