radicle_protocol/fetcher/
state.rs1pub mod command;
9pub mod event;
10
11pub use command::Command;
12pub use event::Event;
13use radicle::storage::refs::FeatureLevel;
14
15use std::collections::{BTreeMap, VecDeque};
16use std::num::NonZeroUsize;
17use std::time;
18
19use radicle_core::{NodeId, RepoId};
20
21use crate::fetcher::RefsToFetch;
22use crate::service::FETCH_TIMEOUT;
23
24pub const MAX_FETCH_QUEUE_SIZE: usize = 128;
26pub const MAX_CONCURRENCY: NonZeroUsize = NonZeroUsize::MIN;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
35pub struct FetchConfig {
36 timeout: time::Duration,
37 protocol: radicle_fetch::Config,
38}
39
40impl FetchConfig {
41 pub fn new() -> Self {
43 Self {
44 timeout: FETCH_TIMEOUT,
45 protocol: radicle_fetch::Config::default(),
46 }
47 }
48
49 pub fn with_timeout(mut self, timeout: time::Duration) -> Self {
51 self.timeout = timeout;
52 self
53 }
54
55 pub fn with_fetch_config(mut self, config: radicle_fetch::Config) -> Self {
57 self.protocol = config;
58 self
59 }
60
61 pub fn with_minimum_feature_level(mut self, feature_level: FeatureLevel) -> Self {
64 self.protocol.level_min = feature_level;
65 self
66 }
67
68 pub fn timeout(&self) -> time::Duration {
70 self.timeout
71 }
72
73 pub fn fetch_config(&self) -> radicle_fetch::Config {
75 self.protocol
76 }
77
78 fn merge(&mut self, other: FetchConfig) {
85 self.timeout = self.timeout.max(other.timeout);
86 self.protocol.limit.refs = self.protocol.limit.refs.max(other.protocol.limit.refs);
87 self.protocol.limit.special = self
88 .protocol
89 .limit
90 .special
91 .max(other.protocol.limit.special);
92 self.protocol.level_min = self.protocol.level_min.min(other.protocol.level_min);
93 }
94}
95
96impl Default for FetchConfig {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
121pub struct FetcherState {
122 active: BTreeMap<RepoId, ActiveFetch>,
124 queues: BTreeMap<NodeId, Queue>,
126 config: Config,
128}
129
130impl Default for FetcherState {
131 fn default() -> Self {
132 Self::new(Config::default())
133 }
134}
135
136impl FetcherState {
137 pub fn new(config: Config) -> Self {
139 Self {
140 active: BTreeMap::new(),
141 queues: BTreeMap::new(),
142 config,
143 }
144 }
145}
146
147impl FetcherState {
148 pub fn handle(&mut self, command: Command) -> Event {
154 match command {
155 Command::Fetch(fetch) => self.fetch(fetch).into(),
156 Command::Fetched(fetched) => self.fetched(fetched).into(),
157 Command::Cancel(cancel) => self.cancel(cancel).into(),
158 }
159 }
160
161 pub fn fetch(
171 &mut self,
172 command::Fetch {
173 from,
174 rid,
175 refs,
176 config,
177 }: command::Fetch,
178 ) -> event::Fetch {
179 if let Some(active) = self.active.get(&rid) {
180 if active.refs == refs && active.from == from {
181 return event::Fetch::AlreadyFetching { rid, from };
182 } else {
183 return self.enqueue(rid, from, refs, config);
184 }
185 }
186
187 if self.is_at_node_capacity(&from) {
188 self.enqueue(rid, from, refs, config)
189 } else {
190 self.active.insert(
191 rid,
192 ActiveFetch {
193 from,
194 refs: refs.clone(),
195 },
196 );
197 event::Fetch::Started {
198 rid,
199 from,
200 refs,
201 config,
202 }
203 }
204 }
205
206 pub fn fetched(&mut self, command::Fetched { from, rid }: command::Fetched) -> event::Fetched {
216 match self.active.get(&rid) {
221 Some(ActiveFetch {
222 from: active_from, ..
223 }) if *active_from == from => match self.active.remove(&rid) {
224 Some(ActiveFetch { from, refs }) => event::Fetched::Completed { from, rid, refs },
225 None => event::Fetched::NotFound { from, rid },
226 },
227 _ => event::Fetched::NotFound { from, rid },
228 }
229 }
230
231 pub fn dequeue(&mut self, from: &NodeId) -> Option<QueuedFetch> {
236 let is_at_capacity = self.is_at_node_capacity(from);
237 let queue = self.queues.get_mut(from)?;
238 let active = &self.active;
239 queue.try_dequeue(|QueuedFetch { rid, .. }| !is_at_capacity && !active.contains_key(rid))
240 }
241
242 pub fn cancel(&mut self, command::Cancel { from }: command::Cancel) -> event::Cancel {
247 let cancelled: Vec<_> = self
248 .active
249 .iter()
250 .filter_map(|(rid, f)| (f.from == from).then_some(*rid))
251 .collect();
252 let ongoing: BTreeMap<_, _> = cancelled
253 .iter()
254 .filter_map(|rid| self.active.remove(rid).map(|f| (*rid, f)))
255 .collect();
256 let ongoing = (!ongoing.is_empty()).then_some(ongoing);
257 let queued = self.queues.remove(&from).filter(|queue| !queue.is_empty());
258
259 match (ongoing, queued) {
260 (None, None) => event::Cancel::Unexpected { from },
261 (ongoing, queued) => event::Cancel::Canceled {
262 from,
263 active: ongoing.unwrap_or_default(),
264 queued: queued.map(|q| q.queue).unwrap_or_default(),
265 },
266 }
267 }
268
269 fn enqueue(
270 &mut self,
271 rid: RepoId,
272 from: NodeId,
273 refs: RefsToFetch,
274 config: FetchConfig,
275 ) -> event::Fetch {
276 let queue = self
277 .queues
278 .entry(from)
279 .or_insert(Queue::new(self.config.maximum_queue_size));
280 match queue.enqueue(QueuedFetch { rid, refs, config }) {
281 Enqueue::CapacityReached(QueuedFetch { rid, refs, config }) => {
282 event::Fetch::QueueAtCapacity {
283 rid,
284 from,
285 refs,
286 config,
287 capacity: queue.len(),
288 }
289 }
290 Enqueue::Queued => event::Fetch::Queued { rid, from },
291 Enqueue::Merged => event::Fetch::Queued { rid, from },
292 }
293 }
294}
295
296impl FetcherState {
297 pub fn queued_fetches(&self) -> &BTreeMap<NodeId, Queue> {
299 &self.queues
300 }
301
302 pub fn active_fetches(&self) -> &BTreeMap<RepoId, ActiveFetch> {
304 &self.active
305 }
306
307 pub fn get_active_fetch(&self, rid: &RepoId) -> Option<&ActiveFetch> {
310 self.active.get(rid)
311 }
312
313 fn is_at_node_capacity(&self, node: &NodeId) -> bool {
319 let count = self.active.values().filter(|f| &f.from == node).count();
320 count >= self.config.maximum_concurrency.into()
321 }
322}
323
324#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
326pub struct Config {
327 maximum_concurrency: NonZeroUsize,
329 maximum_queue_size: MaxQueueSize,
331}
332
333impl Config {
334 pub fn new() -> Self {
335 Self::default()
336 }
337
338 pub fn with_max_capacity(mut self, capacity: MaxQueueSize) -> Self {
340 self.maximum_queue_size = capacity;
341 self
342 }
343
344 pub fn with_max_concurrency(mut self, concurrency: NonZeroUsize) -> Self {
346 self.maximum_concurrency = concurrency;
347 self
348 }
349}
350
351impl Default for Config {
352 fn default() -> Self {
353 Self {
354 maximum_concurrency: MAX_CONCURRENCY,
355 maximum_queue_size: MaxQueueSize::default(),
356 }
357 }
358}
359
360#[derive(Clone, Debug, PartialEq, Eq)]
362pub struct ActiveFetch {
363 pub from: NodeId,
364 pub refs: RefsToFetch,
365}
366
367impl ActiveFetch {
368 pub fn from(&self) -> &NodeId {
370 &self.from
371 }
372
373 pub fn refs(&self) -> &RefsToFetch {
375 &self.refs
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Hash)]
381pub struct QueuedFetch {
382 pub rid: RepoId,
384 pub refs: RefsToFetch,
386 pub config: FetchConfig,
388}
389
390#[derive(Clone, Debug, PartialEq, Eq)]
395pub struct Queue {
396 queue: VecDeque<QueuedFetch>,
397 max_queue_size: MaxQueueSize,
398}
399
400#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
402pub struct MaxQueueSize(usize);
403
404impl MaxQueueSize {
405 pub const MIN: Self = MaxQueueSize(1);
407
408 pub fn new(size: NonZeroUsize) -> Self {
410 Self(size.into())
411 }
412
413 pub fn as_usize(&self) -> usize {
414 self.0
415 }
416
417 fn is_exceeded_by(&self, n: usize) -> bool {
419 n >= self.0
420 }
421}
422
423impl Default for MaxQueueSize {
424 fn default() -> Self {
425 Self(MAX_FETCH_QUEUE_SIZE)
426 }
427}
428
429#[must_use]
431#[derive(Debug, PartialEq, Eq)]
432pub(super) enum Enqueue {
433 CapacityReached(QueuedFetch),
436 Queued,
438 Merged,
439}
440
441impl Queue {
442 pub(super) fn new(max_queue_size: MaxQueueSize) -> Self {
444 Self {
445 queue: VecDeque::with_capacity(max_queue_size.0),
446 max_queue_size,
447 }
448 }
449
450 pub(super) fn len(&self) -> usize {
452 self.queue.len()
453 }
454
455 pub(super) fn is_empty(&self) -> bool {
457 self.queue.is_empty()
458 }
459
460 pub(super) fn enqueue(&mut self, fetch: QueuedFetch) -> Enqueue {
463 if let Some(existing) = self.queue.iter_mut().find(|qf| qf.rid == fetch.rid) {
464 existing.refs = existing.refs.clone().merge(fetch.refs);
465 existing.config.merge(fetch.config);
466 return Enqueue::Merged;
467 }
468
469 if self.max_queue_size.is_exceeded_by(self.queue.len()) {
470 Enqueue::CapacityReached(fetch)
471 } else {
472 self.queue.push_back(fetch);
473 Enqueue::Queued
474 }
475 }
476
477 pub(super) fn try_dequeue<P>(&mut self, predicate: P) -> Option<QueuedFetch>
480 where
481 P: FnOnce(&QueuedFetch) -> bool,
482 {
483 let fetch = self.dequeue()?;
484 if predicate(&fetch) {
485 Some(fetch)
486 } else {
487 self.queue.push_front(fetch);
488 None
489 }
490 }
491
492 pub(super) fn dequeue(&mut self) -> Option<QueuedFetch> {
494 self.queue.pop_front()
495 }
496
497 pub fn iter<'a>(&'a self) -> QueueIter<'a> {
499 QueueIter {
500 inner: self.queue.iter(),
501 }
502 }
503}
504
505pub struct QueueIter<'a> {
507 inner: std::collections::vec_deque::Iter<'a, QueuedFetch>,
508}
509
510impl<'a> Iterator for QueueIter<'a> {
511 type Item = &'a QueuedFetch;
512
513 fn next(&mut self) -> Option<Self::Item> {
514 self.inner.next()
515 }
516}
517
518impl<'a> IntoIterator for &'a Queue {
519 type Item = &'a QueuedFetch;
520 type IntoIter = QueueIter<'a>;
521
522 fn into_iter(self) -> Self::IntoIter {
523 self.iter()
524 }
525}