Skip to main content

git_policy_cli/
service_retry.rs

1//! What:
2//!  The repositories the service must look at again, and when: those with a claim that another instance holds and that may run
3//!  out its lease while this service sits idle.
4//! Why:
5//!  A restarted service that finds the claims of the instance it replaces cannot take them before their lease is over, and
6//!  nothing wakes it when the lease is: the next commit might be hours away, and the work would sit claimed by a holder that
7//!  died. The work loop asks this schedule how long to wait for a wake before it looks again.
8//!
9//! In TS you'd write (pseudocode):
10//! ```ts
11//! const wait = schedule.nextWait(now); const due = schedule.takeDue(now); schedule.replace(ran, retries, now);
12//! ```
13
14use std::path::PathBuf;
15use std::time::{Duration, Instant};
16
17/// Added to a wait so that the lease is over when the service looks, not exactly at its edge.
18pub const RETRY_SLACK: Duration = Duration::from_secs(1);
19
20/// What:
21///  The repositories to look at again, each with the time to do it.
22/// Why:
23///   One entry per repository, replaced by the newest answer of a round that looked at it.
24///
25/// In TS you'd write (pseudocode):
26/// ```ts
27/// type RetrySchedule = Map<string, number>;
28/// ```
29#[derive(Debug, Default)]
30pub struct RetrySchedule {
31    /// The repositories and when each is due.
32    due: Vec<(PathBuf, Instant)>,
33}
34
35/// Methods over the schedule.
36impl RetrySchedule {
37    /// Whether nothing is waiting to be looked at again.
38    pub fn is_empty(&self) -> bool {
39        return self.due.is_empty();
40    }
41
42    /// How long until the first entry is due (nothing at all when the schedule is empty, zero for one already due).
43    pub fn next_wait(&self, now: Instant) -> Option<Duration> {
44        return self.due.iter().map(|(_, when)| { return when.saturating_duration_since(now); }).min();
45    }
46
47    /// Removes and returns the repositories that are due at `now`.
48    pub fn take_due(&mut self, now: Instant) -> Vec<PathBuf> {
49        let (due, waiting): (Vec<(PathBuf, Instant)>, Vec<(PathBuf, Instant)>) = std::mem::take(&mut self.due).into_iter().partition(|(_, when)| { return *when <= now; });
50        self.due = waiting;
51        return due.into_iter().map(|(directory, _)| { return directory; }).collect();
52    }
53
54    /// Forgets what was scheduled for the repositories a round just ran, and schedules what that round asked for.
55    pub fn replace(&mut self, ran: &[PathBuf], retries: Vec<(PathBuf, Duration)>, now: Instant) {
56        self.due.retain(|(directory, _)| { return !ran.contains(directory); });
57        for (directory, wait) in retries {
58            self.due.push((directory, now + wait + RETRY_SLACK));
59        }
60    }
61}
62
63/// Schedule controls stay out of the release executable.
64#[cfg(test)]
65#[path = "service_retry_tests.rs"]
66mod tests;