tor_circmgr/hspool/
pool.rs1use std::time::{Duration, Instant};
4
5use crate::{
6 hspool::{HsCircStem, HsCircStemKind},
7 AbstractCirc,
8};
9use rand::Rng;
10use tor_basic_utils::RngExt as _;
11
12pub(super) struct Pool<C: AbstractCirc> {
14 circuits: Vec<HsCircStem<C>>,
16
17 stem_target: usize,
19
20 guarded_stem_target: usize,
22
23 have_been_exhausted: bool,
26
27 have_been_under_highwater: bool,
30
31 last_changed_target: Option<Instant>,
33}
34
35const DEFAULT_NAIVE_STEM_TARGET: usize = 3;
37
38const DEFAULT_GUARDED_STEM_TARGET: usize = 1;
40
41const MAX_NAIVE_STEM_TARGET: usize = 384;
44
45const MAX_GUARDED_STEM_TARGET: usize = 128;
48
49pub(super) struct ForLaunch<'a> {
54 kind: HsCircStemKind,
56 count: &'a mut usize,
61}
62
63impl<'a> ForLaunch<'a> {
64 pub(super) fn note_circ_launched(self) {
66 *self.count -= 1;
67 }
68
69 pub(super) fn kind(&self) -> HsCircStemKind {
71 self.kind
72 }
73}
74
75pub(super) struct CircsToLaunch {
77 stem_target: usize,
79 guarded_stem_target: usize,
81}
82
83impl CircsToLaunch {
84 pub(super) fn for_launch(&mut self) -> ForLaunch {
86 if self.stem_target > 0 {
88 ForLaunch {
89 kind: HsCircStemKind::Naive,
90 count: &mut self.stem_target,
91 }
92 } else {
93 ForLaunch {
95 kind: HsCircStemKind::Guarded,
96 count: &mut self.guarded_stem_target,
97 }
98 }
99 }
100
101 pub(super) fn stem(&self) -> usize {
103 self.stem_target
104 }
105
106 pub(super) fn guarded_stem(&self) -> usize {
108 self.guarded_stem_target
109 }
110
111 pub(super) fn n_to_launch(&self) -> usize {
113 self.stem_target + self.guarded_stem_target
114 }
115}
116
117impl<C: AbstractCirc> Default for Pool<C> {
118 fn default() -> Self {
119 Self {
120 circuits: Vec::new(),
121 stem_target: DEFAULT_NAIVE_STEM_TARGET,
122 guarded_stem_target: DEFAULT_GUARDED_STEM_TARGET,
123 have_been_exhausted: false,
124 have_been_under_highwater: false,
125 last_changed_target: None,
126 }
127 }
128}
129
130impl<C: AbstractCirc> Pool<C> {
131 pub(super) fn insert(&mut self, circ: HsCircStem<C>) {
133 self.circuits.push(circ);
134 }
135
136 pub(super) fn retain<F>(&mut self, f: F)
138 where
139 F: FnMut(&HsCircStem<C>) -> bool,
140 {
141 self.circuits.retain(f);
142 }
143
144 pub(super) fn very_low(&self) -> bool {
146 self.circuits.len() <= self.target() / 3
147 }
148
149 pub(super) fn circs_to_launch(&self) -> CircsToLaunch {
151 CircsToLaunch {
152 stem_target: self.stems_to_launch(),
153 guarded_stem_target: self.guarded_stems_to_launch(),
154 }
155 }
156
157 fn stems_to_launch(&self) -> usize {
159 let circ_count = self
160 .circuits
161 .iter()
162 .filter(|c| c.kind == HsCircStemKind::Naive)
163 .count();
164
165 self.stem_target.saturating_sub(circ_count)
166 }
167
168 fn guarded_stems_to_launch(&self) -> usize {
170 let circ_count = self
171 .circuits
172 .iter()
173 .filter(|c| c.kind == HsCircStemKind::Guarded)
174 .count();
175
176 self.guarded_stem_target.saturating_sub(circ_count)
177 }
178
179 fn target(&self) -> usize {
184 self.stem_target + self.guarded_stem_target
185 }
186
187 pub(super) fn take_one_where<R, F>(
194 &mut self,
195 rng: &mut R,
196 f: F,
197 prefs: &HsCircPrefs,
198 ) -> Option<HsCircStem<C>>
199 where
200 R: Rng,
201 F: Fn(&HsCircStem<C>) -> bool,
202 {
203 let rv = match random_idx_where(rng, &mut self.circuits[..], |circ_stem| {
204 circ_stem.satisfies_prefs(prefs) && f(circ_stem)
206 })
207 .or_else(|| {
208 random_idx_where(rng, &mut self.circuits[..], f)
210 }) {
211 Some(idx) => Some(self.circuits.swap_remove(idx)),
212 None => None,
213 };
214
215 if self.circuits.is_empty() {
216 self.have_been_exhausted = true;
217 self.have_been_under_highwater = true;
218 } else if self.circuits.len() < self.target() * 4 / 5 {
219 self.have_been_under_highwater = true;
220 }
221
222 rv
223 }
224
225 pub(super) fn update_target_size(&mut self, now: Instant) {
229 const MIN_TIME_TO_GROW: Duration = Duration::from_secs(120);
234 const MIN_TIME_TO_SHRINK: Duration = Duration::from_secs(600);
239
240 let last_changed = self.last_changed_target.get_or_insert(now);
241 let time_since_last_change = now.saturating_duration_since(*last_changed);
242
243 if self.have_been_exhausted {
252 if time_since_last_change < MIN_TIME_TO_GROW {
253 return;
254 }
255 self.stem_target *= 2;
256 self.guarded_stem_target *= 2;
257 } else if !self.have_been_under_highwater {
258 if time_since_last_change < MIN_TIME_TO_SHRINK {
259 return;
260 }
261
262 self.stem_target /= 2;
263 self.guarded_stem_target /= 2;
264 }
265 self.last_changed_target = Some(now);
266 self.stem_target = self
267 .stem_target
268 .clamp(DEFAULT_NAIVE_STEM_TARGET, MAX_NAIVE_STEM_TARGET);
269 self.guarded_stem_target = self
270 .guarded_stem_target
271 .clamp(DEFAULT_GUARDED_STEM_TARGET, MAX_GUARDED_STEM_TARGET);
272 self.have_been_exhausted = false;
273 self.have_been_under_highwater = false;
274 }
275
276 #[allow(clippy::unnecessary_wraps)] pub(super) fn retire_all_circuits(&mut self) -> Result<(), tor_config::ReconfigureError> {
279 self.have_been_exhausted = true;
280
281 self.circuits.clear();
283
284 Ok(())
285 }
286}
287
288#[derive(Default, Debug, Clone)]
290pub(super) struct HsCircPrefs {
291 pub(super) kind_prefs: Option<HsCircStemKind>,
293}
294
295impl HsCircPrefs {
296 pub(super) fn preferred_stem_kind(&mut self, kind: HsCircStemKind) -> &mut Self {
298 self.kind_prefs = Some(kind);
299 self
300 }
301}
302
303fn random_idx_where<R, T, P>(rng: &mut R, mut slice: &mut [T], predicate: P) -> Option<usize>
309where
310 R: Rng,
311 P: Fn(&T) -> bool,
312{
313 while !slice.is_empty() {
314 let idx = rng
315 .gen_range_checked(0..slice.len())
316 .expect("slice was not empty but is now empty");
317 if predicate(&slice[idx]) {
318 return Some(idx);
319 }
320 let last_idx = slice.len() - 1;
321 slice.swap(idx, last_idx);
324 slice = &mut slice[..last_idx];
325 }
326 None
328}
329
330#[cfg(test)]
331mod test {
332 #![allow(clippy::bool_assert_comparison)]
334 #![allow(clippy::clone_on_copy)]
335 #![allow(clippy::dbg_macro)]
336 #![allow(clippy::mixed_attributes_style)]
337 #![allow(clippy::print_stderr)]
338 #![allow(clippy::print_stdout)]
339 #![allow(clippy::single_char_pattern)]
340 #![allow(clippy::unwrap_used)]
341 #![allow(clippy::unchecked_duration_subtraction)]
342 #![allow(clippy::useless_vec)]
343 #![allow(clippy::needless_pass_by_value)]
344 use super::*;
347 use tor_basic_utils::test_rng::testing_rng;
348
349 #[test]
350 fn random_idx() {
351 let mut rng = testing_rng();
352 let mut orig_numbers: Vec<i32> = vec![1, 3, 4, 8, 11, 19, 12, 6, 27];
353 let mut numbers = orig_numbers.clone();
354
355 let mut found: std::collections::HashMap<i32, bool> =
356 numbers.iter().map(|n| (*n, false)).collect();
357
358 for _ in 0..1000 {
359 let idx = random_idx_where(&mut rng, &mut numbers[..], |n| n & 1 == 1).unwrap();
360 assert!(numbers[idx] & 1 == 1);
361 found.insert(numbers[idx], true);
362 }
363
364 for num in numbers.iter() {
365 assert!(found[num] == (num & 1 == 1));
366 }
367
368 numbers.sort();
370 orig_numbers.sort();
371 assert_eq!(numbers, orig_numbers);
372 }
373
374 #[test]
375 fn random_idx_empty() {
376 let mut rng = testing_rng();
377 let idx = random_idx_where(&mut rng, &mut [], |_: &i32| panic!());
378 assert_eq!(idx, None);
379 }
380
381 #[test]
382 fn random_idx_none() {
383 let mut rng = testing_rng();
384 let mut numbers: Vec<i32> = vec![1, 3, 4, 8, 11, 19, 12, 6, 27];
385 assert_eq!(
386 random_idx_where(&mut rng, &mut numbers[..], |_: &i32| false),
387 None
388 );
389 }
390}