use std::time::Duration;
const MIN_TTL_TO_RENEW_RATIO: u32 = 3;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LeaseTimings {
ttl: Duration,
renew_interval: Duration,
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum LeaseTimingsError {
#[error("lease ttl must be at least 1ms")]
TtlTooSmall,
#[error("lease renew interval must be at least 1ms")]
RenewIntervalTooSmall,
#[error(
"lease ttl ({ttl:?}) must be at least {MIN_TTL_TO_RENEW_RATIO}x the renew interval \
({renew_interval:?}) so an owner can miss renewals without losing a live lease"
)]
TtlRenewRatioTooSmall {
ttl: Duration,
renew_interval: Duration,
},
}
impl LeaseTimings {
pub fn new(ttl: Duration, renew_interval: Duration) -> Result<Self, LeaseTimingsError> {
if ttl.as_millis() == 0 {
return Err(LeaseTimingsError::TtlTooSmall);
}
if renew_interval.as_millis() == 0 {
return Err(LeaseTimingsError::RenewIntervalTooSmall);
}
if ttl < renew_interval.saturating_mul(MIN_TTL_TO_RENEW_RATIO) {
return Err(LeaseTimingsError::TtlRenewRatioTooSmall {
ttl,
renew_interval,
});
}
Ok(Self {
ttl,
renew_interval,
})
}
pub fn from_ttl(ttl: Duration) -> Result<Self, LeaseTimingsError> {
Self::new(ttl, ttl / MIN_TTL_TO_RENEW_RATIO)
}
pub fn ttl(&self) -> Duration {
self.ttl
}
pub fn renew_interval(&self) -> Duration {
self.renew_interval
}
pub fn ttl_ms(&self) -> u64 {
duration_to_ms(self.ttl)
}
pub fn renew_interval_ms(&self) -> u64 {
duration_to_ms(self.renew_interval)
}
}
impl Default for LeaseTimings {
fn default() -> Self {
Self {
ttl: Duration::from_secs(30),
renew_interval: Duration::from_secs(10),
}
}
}
fn duration_to_ms(duration: Duration) -> u64 {
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_lease_timings_keep_the_contractual_windows() {
let timings = LeaseTimings::default();
assert_eq!(timings.ttl_ms(), 30_000);
assert_eq!(timings.renew_interval_ms(), 10_000);
assert_eq!(
timings.ttl_ms(),
timings.renew_interval_ms() * u64::from(MIN_TTL_TO_RENEW_RATIO)
);
}
#[test]
fn constructor_enforces_ttl_renew_ratio() {
assert!(LeaseTimings::new(Duration::from_secs(30), Duration::from_secs(10)).is_ok());
assert_eq!(
LeaseTimings::new(Duration::from_secs(29), Duration::from_secs(10)),
Err(LeaseTimingsError::TtlRenewRatioTooSmall {
ttl: Duration::from_secs(29),
renew_interval: Duration::from_secs(10),
})
);
assert_eq!(
LeaseTimings::new(Duration::ZERO, Duration::from_secs(1)),
Err(LeaseTimingsError::TtlTooSmall)
);
assert_eq!(
LeaseTimings::new(Duration::from_secs(30), Duration::from_micros(500)),
Err(LeaseTimingsError::RenewIntervalTooSmall)
);
}
#[test]
fn from_ttl_derives_the_boundary_renew_interval() {
let timings = LeaseTimings::from_ttl(Duration::from_millis(60)).expect("valid timings");
assert_eq!(timings.ttl_ms(), 60);
assert_eq!(timings.renew_interval_ms(), 20);
assert_eq!(
LeaseTimings::from_ttl(Duration::from_millis(2)),
Err(LeaseTimingsError::RenewIntervalTooSmall)
);
}
}