lash_core/store/
lease_timings.rs1use std::time::Duration;
4
5const MIN_TTL_TO_RENEW_RATIO: u32 = 3;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct LeaseTimings {
34 ttl: Duration,
35 renew_interval: Duration,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
40pub enum LeaseTimingsError {
41 #[error("lease ttl must be at least 1ms")]
42 TtlTooSmall,
43 #[error("lease renew interval must be at least 1ms")]
44 RenewIntervalTooSmall,
45 #[error(
46 "lease ttl ({ttl:?}) must be at least {MIN_TTL_TO_RENEW_RATIO}x the renew interval \
47 ({renew_interval:?}) so an owner can miss renewals without losing a live lease"
48 )]
49 TtlRenewRatioTooSmall {
50 ttl: Duration,
51 renew_interval: Duration,
52 },
53}
54
55impl LeaseTimings {
56 pub fn new(ttl: Duration, renew_interval: Duration) -> Result<Self, LeaseTimingsError> {
60 if ttl.as_millis() == 0 {
61 return Err(LeaseTimingsError::TtlTooSmall);
62 }
63 if renew_interval.as_millis() == 0 {
64 return Err(LeaseTimingsError::RenewIntervalTooSmall);
65 }
66 if ttl < renew_interval.saturating_mul(MIN_TTL_TO_RENEW_RATIO) {
67 return Err(LeaseTimingsError::TtlRenewRatioTooSmall {
68 ttl,
69 renew_interval,
70 });
71 }
72 Ok(Self {
73 ttl,
74 renew_interval,
75 })
76 }
77
78 pub fn from_ttl(ttl: Duration) -> Result<Self, LeaseTimingsError> {
81 Self::new(ttl, ttl / MIN_TTL_TO_RENEW_RATIO)
82 }
83
84 pub fn ttl(&self) -> Duration {
85 self.ttl
86 }
87
88 pub fn renew_interval(&self) -> Duration {
89 self.renew_interval
90 }
91
92 pub fn ttl_ms(&self) -> u64 {
95 duration_to_ms(self.ttl)
96 }
97
98 pub fn renew_interval_ms(&self) -> u64 {
99 duration_to_ms(self.renew_interval)
100 }
101}
102
103impl Default for LeaseTimings {
104 fn default() -> Self {
105 Self {
106 ttl: Duration::from_secs(30),
107 renew_interval: Duration::from_secs(10),
108 }
109 }
110}
111
112fn duration_to_ms(duration: Duration) -> u64 {
113 u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn default_lease_timings_keep_the_contractual_windows() {
122 let timings = LeaseTimings::default();
123 assert_eq!(timings.ttl_ms(), 30_000);
124 assert_eq!(timings.renew_interval_ms(), 10_000);
125 assert_eq!(
126 timings.ttl_ms(),
127 timings.renew_interval_ms() * u64::from(MIN_TTL_TO_RENEW_RATIO)
128 );
129 }
130
131 #[test]
132 fn constructor_enforces_ttl_renew_ratio() {
133 assert!(LeaseTimings::new(Duration::from_secs(30), Duration::from_secs(10)).is_ok());
134 assert_eq!(
135 LeaseTimings::new(Duration::from_secs(29), Duration::from_secs(10)),
136 Err(LeaseTimingsError::TtlRenewRatioTooSmall {
137 ttl: Duration::from_secs(29),
138 renew_interval: Duration::from_secs(10),
139 })
140 );
141 assert_eq!(
142 LeaseTimings::new(Duration::ZERO, Duration::from_secs(1)),
143 Err(LeaseTimingsError::TtlTooSmall)
144 );
145 assert_eq!(
146 LeaseTimings::new(Duration::from_secs(30), Duration::from_micros(500)),
147 Err(LeaseTimingsError::RenewIntervalTooSmall)
148 );
149 }
150
151 #[test]
152 fn from_ttl_derives_the_boundary_renew_interval() {
153 let timings = LeaseTimings::from_ttl(Duration::from_millis(60)).expect("valid timings");
154 assert_eq!(timings.ttl_ms(), 60);
155 assert_eq!(timings.renew_interval_ms(), 20);
156 assert_eq!(
157 LeaseTimings::from_ttl(Duration::from_millis(2)),
158 Err(LeaseTimingsError::RenewIntervalTooSmall)
159 );
160 }
161}