hopr_transport_session/balancer/mod.rs
1mod controller;
2/// Contains implementation of the [`SurbBalancerController`] trait using a Proportional Integral Derivative (PID)
3/// controller.
4pub mod pid;
5#[allow(dead_code)]
6mod rate_limiting;
7/// Contains a simple proportional output implementation of the [`SurbBalancerController`] trait.
8pub mod simple;
9
10pub use controller::{BalancerStateValues, SurbBalancer, SurbBalancerConfig};
11pub use rate_limiting::{RateController, RateLimitSinkExt, RateLimitStreamExt};
12
13/// Smallest possible interval for balancer sampling.
14pub const MIN_BALANCER_SAMPLING_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100);
15
16/// Allows estimating the flow of SURBs in a Session (production or consumption).
17pub trait SurbFlowEstimator {
18 /// Estimates the number of SURBs consumed.
19 ///
20 /// Value returned on each call must be equal or greater to the value returned by a previous call.
21 fn estimate_surbs_consumed(&self) -> u64;
22 /// Estimates the number of SURBs produced or received.
23 ///
24 /// Value returned on each call must be equal or greater to the value returned by a previous call.
25 fn estimate_surbs_produced(&self) -> u64;
26
27 /// Subtracts SURBs consumed from SURBs produced, saturating at zero.
28 fn saturating_diff(&self) -> u64 {
29 self.estimate_surbs_produced()
30 .saturating_sub(self.estimate_surbs_consumed())
31 }
32
33 /// Computes the estimated change in SURB buffer.
34 ///
35 /// This is done by computing the change in produced and consumed SURBs since the `earlier`
36 /// state and then taking their difference.
37 ///
38 /// A positive result is a surplus number of SURBs added to the buffer, a negative result is a loss of SURBs
39 /// from the buffer.
40 /// Returns `None` if `earlier` had more SURBs produced/consumed than this instance (overflow).
41 fn estimated_surb_buffer_change<E: SurbFlowEstimator>(&self, earlier: &E) -> Option<i64> {
42 match (
43 self.estimate_surbs_produced()
44 .checked_sub(earlier.estimate_surbs_produced()),
45 self.estimate_surbs_consumed()
46 .checked_sub(earlier.estimate_surbs_consumed()),
47 ) {
48 (Some(surbs_delivered_delta), Some(surbs_consumed_delta)) => {
49 Some(surbs_delivered_delta as i64 - surbs_consumed_delta as i64)
50 }
51 _ => None,
52 }
53 }
54}
55
56/// Allows controlling the production or consumption of SURBs in a Session.
57#[cfg_attr(test, mockall::automock)]
58pub trait SurbFlowController {
59 /// Adjusts the amount of SURB production or consumption.
60 fn adjust_surb_flow(&self, surbs_per_sec: usize);
61}
62
63/// Represents the setpoint (target) and output limit of a controller.
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
65pub struct BalancerControllerBounds(u64, u64);
66
67impl BalancerControllerBounds {
68 /// Creates a new instance.
69 pub fn new(target: u64, output_limit: u64) -> Self {
70 Self(target, output_limit)
71 }
72
73 /// Gets the target (setpoint) of a controller.
74 #[inline]
75 pub fn target(&self) -> u64 {
76 self.0
77 }
78
79 /// Gets the output limit of a controller.
80 #[inline]
81 pub fn output_limit(&self) -> u64 {
82 self.1
83 }
84
85 /// Unpacks the controller bounds into two `u64`s (target and output limit).
86 #[inline]
87 pub fn unzip(&self) -> (u64, u64) {
88 (self.0, self.1)
89 }
90}
91
92/// Trait abstracting a controller used in the [`SurbBalancer`].
93pub trait SurbBalancerController {
94 /// Gets the current bounds of the controller.
95 fn bounds(&self) -> BalancerControllerBounds;
96 /// Updates the controller's target (setpoint) and output limit.
97 fn set_target_and_limit(&mut self, bounds: BalancerControllerBounds);
98 /// Queries the controller for the next control output based on the `current_buffer_level` of SURBs.
99 fn next_control_output(&mut self, current_buffer_level: u64) -> u64;
100 /// Discards accumulated history, leaving the bounds intact.
101 ///
102 /// Used when the buffer estimate the controller has been acting on stops meaning what it meant
103 /// -- crossing into or out of a period where the counterparty could not be observed at all.
104 /// Carrying the error accumulated under the old regime into the new one makes the controller
105 /// answer a question nobody asked.
106 fn reset(&mut self);
107}
108
109/// Implementation of [`SurbFlowEstimator`] that tracks the number of produced
110/// and consumed SURBs via two `u64`s.
111///
112/// This implementation can take "snapshots" of other `SurbFlowEstimators` (via `From` trait) by simply
113/// calling their respective methods to fill in its values.
114#[derive(Clone, Copy, Debug, Default)]
115pub struct SimpleSurbFlowEstimator {
116 /// Number of produced SURBs.
117 pub produced: u64,
118 /// Number of consumed SURBs.
119 pub consumed: u64,
120}
121
122impl<T: SurbFlowEstimator> From<&T> for SimpleSurbFlowEstimator {
123 fn from(value: &T) -> Self {
124 Self {
125 produced: value.estimate_surbs_produced(),
126 consumed: value.estimate_surbs_consumed(),
127 }
128 }
129}
130
131impl SurbFlowEstimator for SimpleSurbFlowEstimator {
132 fn estimate_surbs_consumed(&self) -> u64 {
133 self.consumed
134 }
135
136 fn estimate_surbs_produced(&self) -> u64 {
137 self.produced
138 }
139}
140
141/// An implementation of `SurbFlowEstimator` that tracks the number of produced
142/// and consumed SURBs via two `AtomicU64`s.
143#[derive(Clone, Debug, Default)]
144pub struct AtomicSurbFlowEstimator {
145 /// Number of consumed SURBs.
146 pub consumed: std::sync::Arc<std::sync::atomic::AtomicU64>,
147 /// Number of produced or received SURBs.
148 pub produced: std::sync::Arc<std::sync::atomic::AtomicU64>,
149}
150
151impl SurbFlowEstimator for AtomicSurbFlowEstimator {
152 fn estimate_surbs_consumed(&self) -> u64 {
153 self.consumed.load(std::sync::atomic::Ordering::Relaxed)
154 }
155
156 fn estimate_surbs_produced(&self) -> u64 {
157 self.produced.load(std::sync::atomic::Ordering::Relaxed)
158 }
159}
160
161/// Wraps a [`RateController`] as [`SurbFlowController`] with the given correction
162/// factor on time unit.
163///
164/// For example, when this is used to control the flow of keep-alive messages (carrying SURBs),
165/// the correction factor is `HoprPacket::MAX_SURBS_IN_PACKET` - which is the number of SURBs
166/// a single keep-alive message can bear.
167///
168/// In another case, when this is used to control the egress of a Session, each outgoing packet
169/// consumes only a single SURB and therefore the correction factor is `1`.
170pub struct SurbControllerWithCorrection(pub RateController, pub u32);
171
172impl SurbFlowController for SurbControllerWithCorrection {
173 fn adjust_surb_flow(&self, surbs_per_sec: usize) {
174 self.0
175 .set_rate_per_unit(surbs_per_sec, self.1 * std::time::Duration::from_secs(1));
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_estimated_surb_buffer_change() {
185 let estimator_1 = SimpleSurbFlowEstimator {
186 produced: 10,
187 consumed: 5,
188 };
189 let estimator_2 = SimpleSurbFlowEstimator {
190 produced: 15,
191 consumed: 11,
192 };
193 let estimator_3 = SimpleSurbFlowEstimator {
194 produced: 25,
195 consumed: 16,
196 };
197 assert_eq!(estimator_1.estimated_surb_buffer_change(&estimator_1), Some(0));
198 assert_eq!(estimator_2.estimated_surb_buffer_change(&estimator_1), Some(-1));
199 assert_eq!(estimator_3.estimated_surb_buffer_change(&estimator_2), Some(5));
200 assert_eq!(estimator_1.estimated_surb_buffer_change(&estimator_2), None);
201 }
202}