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