Skip to main content

origin_sync/
policy.rs

1use crate::Backoff;
2use time::Duration;
3
4/// When a target should be synchronised.
5///
6/// The same engine serves a one-minute notification poll and a six-hour analytics
7/// refresh — only these numbers differ.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct SyncPolicy {
10    /// Cadence while everything is healthy.
11    pub interval: Duration,
12
13    /// Floor between two runs, however often a refresh is requested.
14    ///
15    /// Protects against a user holding the refresh button and against a UI that
16    /// re-syncs on every window focus.
17    pub min_interval: Duration,
18
19    /// How to back off after failures.
20    pub backoff: Backoff,
21
22    /// Retry delay while the machine appears to be offline.
23    ///
24    /// Short and flat rather than exponential: connectivity usually returns in one
25    /// step, and backing off for half an hour would leave the app stale long after
26    /// the network came back.
27    pub offline_retry: Duration,
28
29    /// The longest a service may push this target's next run into the future.
30    ///
31    /// A service-reported throttle (quota reset, `X-Poll-Interval`) is honoured up
32    /// to this bound. Without it, a buggy or hostile response could freeze a target
33    /// indefinitely; past the bound the delay is clamped and a warning is logged.
34    pub max_throttle: Duration,
35}
36
37impl Default for SyncPolicy {
38    fn default() -> Self {
39        Self {
40            interval: Duration::minutes(5),
41            min_interval: Duration::seconds(30),
42            backoff: Backoff::default(),
43            offline_retry: Duration::seconds(20),
44            max_throttle: Duration::hours(24),
45        }
46    }
47}
48
49impl SyncPolicy {
50    /// A policy that runs every `interval`.
51    pub fn every(interval: Duration) -> Self {
52        Self {
53            interval,
54            // A floor of a tenth of the cadence, but never below five seconds.
55            min_interval: (interval / 10_i32).max(Duration::seconds(5)),
56            ..Self::default()
57        }
58    }
59
60    pub fn with_min_interval(mut self, min_interval: Duration) -> Self {
61        self.min_interval = min_interval;
62        self
63    }
64
65    pub fn with_backoff(mut self, backoff: Backoff) -> Self {
66        self.backoff = backoff;
67        self
68    }
69
70    pub fn with_offline_retry(mut self, offline_retry: Duration) -> Self {
71        self.offline_retry = offline_retry;
72        self
73    }
74
75    pub fn with_max_throttle(mut self, max_throttle: Duration) -> Self {
76        self.max_throttle = max_throttle;
77        self
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn a_fast_cadence_still_keeps_a_sane_floor() {
87        assert_eq!(
88            SyncPolicy::every(Duration::seconds(10)).min_interval,
89            Duration::seconds(5)
90        );
91    }
92
93    #[test]
94    fn a_slow_cadence_scales_its_floor() {
95        assert_eq!(
96            SyncPolicy::every(Duration::hours(1)).min_interval,
97            Duration::minutes(6)
98        );
99    }
100}