pub const MAX_LEASE_MS: u64 = 3_600_000;
pub const JITTER_DIVISOR: u64 = 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Action {
Renew,
Acquire,
Wait,
}
#[must_use]
pub const fn timing_valid(lease_ms: u64, renew_ms: u64, retry_ms: u64) -> bool {
if lease_ms > MAX_LEASE_MS || retry_ms == 0 || lease_ms <= renew_ms || retry_ms >= renew_ms {
return false;
}
renew_ms * 5 > retry_ms * 6
}
#[must_use]
pub const fn jittered_ms(base_ms: u64, rand: u64) -> u64 {
let span = base_ms / JITTER_DIVISOR + 1;
base_ms.saturating_add(rand % span)
}
#[must_use]
pub const fn decide(
holder_is_me: bool,
holder_empty: bool,
observed_age_ms: u64,
duration_ms: u64,
) -> Action {
if holder_is_me {
Action::Renew
} else if holder_empty || observed_age_ms >= duration_ms {
Action::Acquire
} else {
Action::Wait
}
}
#[must_use]
pub const fn effective_duration_ms(record_secs: i32, fallback_ms: u64) -> u64 {
if record_secs > 0 {
let ms = record_secs.unsigned_abs() as u64 * 1000;
if ms < MAX_LEASE_MS { ms } else { MAX_LEASE_MS }
} else {
fallback_ms
}
}
#[must_use]
pub const fn next_transitions(old: u32, holder_is_me: bool) -> u32 {
if holder_is_me {
old
} else {
old.saturating_add(1)
}
}
#[must_use]
pub const fn observed_at(prev_at: Option<u64>, changed: bool, now_ms: u64) -> u64 {
match prev_at {
Some(at) if !changed => at,
_ => now_ms,
}
}
#[must_use]
pub const fn observed_age_ms(now_ms: u64, at_ms: u64) -> u64 {
now_ms.saturating_sub(at_ms)
}
#[must_use]
pub const fn leading(now_ms: u64, sent_ms: Option<u64>, renew_ms: u64, holder_is_me: bool) -> bool {
match sent_ms {
Some(s) => holder_is_me && (now_ms < s || now_ms - s < renew_ms),
None => false,
}
}
#[must_use]
pub const fn grace_period_secs(
prestop_hook: u64,
prestop_grace: u64,
shutdown_timeout: u64,
buffer: u64,
) -> u64 {
prestop_hook
.saturating_add(prestop_grace)
.saturating_add(shutdown_timeout)
.saturating_add(buffer)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn spec_timing_valid(lease: i128, renew: i128, retry: i128) -> bool {
retry > 0 && renew * 5 > retry * 6 && lease > renew && lease <= i128::from(MAX_LEASE_MS)
}
fn valid_timing() -> impl Strategy<Value = (u64, u64, u64)> {
(1..=MAX_LEASE_MS / 2)
.prop_flat_map(|retry| (Just(retry), (retry * 6 / 5 + 1)..MAX_LEASE_MS))
.prop_flat_map(|(retry, renew)| ((renew + 1)..=MAX_LEASE_MS, Just(renew), Just(retry)))
}
fn spec_leading(now: i128, sent: Option<i128>, renew: i128, me: bool) -> bool {
me && sent.is_some_and(|s| now < s + renew)
}
#[test]
fn verus_constants_match() {
let spec = include_str!("../verus/policy.rs");
for line in [
"pub const MAX_LEASE_MS: u64 = 3_600_000;".to_owned(),
format!("pub const JITTER_DIVISOR: u64 = {JITTER_DIVISOR};"),
] {
assert!(spec.contains(&line), "verus/policy.rs lacks: {line}");
}
assert_eq!(MAX_LEASE_MS, 3_600_000);
}
#[test]
fn default_timing_is_valid() {
assert!(timing_valid(15_000, 10_000, 2_000));
}
#[test]
fn timing_rejects_each_bad_rule() {
assert!(!timing_valid(15_000, 10_000, 0), "retry 0");
assert!(!timing_valid(10_000, 10_000, 2_000), "lease == renew");
assert!(!timing_valid(15_000, 12_000, 10_000), "renew <= 1.2 retry");
assert!(
!timing_valid(MAX_LEASE_MS + 1, 10_000, 2_000),
"lease too long"
);
assert!(!timing_valid(15_000, 10_000, u64::MAX));
}
#[test]
fn decide_table() {
assert_eq!(decide(true, false, 0, 15_000), Action::Renew);
assert_eq!(decide(false, true, 0, 15_000), Action::Acquire);
assert_eq!(decide(false, false, 15_000, 15_000), Action::Acquire);
assert_eq!(decide(false, false, 14_999, 15_000), Action::Wait);
}
#[test]
fn transitions_grow_only_on_holder_change() {
assert_eq!(next_transitions(3, true), 3);
assert_eq!(next_transitions(3, false), 4);
assert_eq!(next_transitions(u32::MAX, false), u32::MAX);
}
#[test]
fn jitter_edges() {
assert_eq!(jittered_ms(1_000, 200), 1_200, "top of the range");
assert_eq!(jittered_ms(1_000, 201), 1_000, "wraps to the bottom");
assert_eq!(jittered_ms(0, 7), 0);
}
#[test]
fn record_duration_is_capped() {
assert_eq!(effective_duration_ms(i32::MAX, 15_000), MAX_LEASE_MS);
assert_eq!(effective_duration_ms(3_600, 15_000), MAX_LEASE_MS);
assert_eq!(effective_duration_ms(3_601, 15_000), MAX_LEASE_MS);
}
#[test]
fn effective_duration_prefers_record() {
assert_eq!(effective_duration_ms(20, 15_000), 20_000);
assert_eq!(effective_duration_ms(3_599, 15_000), 3_599_000);
assert_eq!(effective_duration_ms(0, 15_000), 15_000);
assert_eq!(effective_duration_ms(-5, 15_000), 15_000);
}
#[test]
fn observation_keeps_time_until_change() {
assert_eq!(observed_at(None, false, 50), 50);
assert_eq!(observed_at(Some(10), false, 50), 10);
assert_eq!(observed_at(Some(10), true, 50), 50);
assert_eq!(observed_age_ms(50, 10), 40);
assert_eq!(observed_age_ms(5, 10), 0);
}
#[test]
fn belief_ends_at_deadline() {
assert!(leading(1_000, Some(0), 10_000, true));
assert!(leading(9_999, Some(0), 10_000, true));
assert!(!leading(10_000, Some(0), 10_000, true));
assert!(!leading(1_000, Some(0), 10_000, false));
assert!(!leading(1_000, None, 10_000, true));
}
#[test]
fn grace_period_follows_autumn_formula() {
assert_eq!(grace_period_secs(5, 5, 30, 10), 50);
assert_eq!(grace_period_secs(u64::MAX, 1, 0, 0), u64::MAX);
}
proptest! {
#[test]
fn timing_valid_matches_spec(lease in 0..=MAX_LEASE_MS + 10, renew in 0..=MAX_LEASE_MS + 10, retry in prop_oneof![0..=MAX_LEASE_MS, Just(u64::MAX)]) {
prop_assert_eq!(
timing_valid(lease, renew, retry),
spec_timing_valid(lease.into(), renew.into(), retry.into())
);
}
#[test]
fn jitter_in_bounds(base in 0..=MAX_LEASE_MS, rand: u64) {
let r = jittered_ms(base, rand);
prop_assert!(base <= r && r <= base + base / JITTER_DIVISOR);
}
#[test]
fn valid_timing_fits_jittered_retry((lease, renew, retry) in valid_timing(), rand: u64) {
prop_assert!(timing_valid(lease, renew, retry));
prop_assert!(jittered_ms(retry, rand) < renew);
}
#[test]
fn decide_matches_spec(me: bool, empty: bool, age: u64, dur: u64) {
let a = decide(me, empty, age, dur);
if me {
prop_assert_eq!(a, Action::Renew);
} else if empty || age >= dur {
prop_assert_eq!(a, Action::Acquire);
} else {
prop_assert_eq!(a, Action::Wait);
}
}
#[test]
fn leading_matches_spec(now: u64, sent: Option<u64>, renew: u64, me: bool) {
prop_assert_eq!(
leading(now, sent, renew, me),
spec_leading(now.into(), sent.map(i128::from), renew.into(), me)
);
}
#[test]
fn no_overlap_on_shared_time_line(
sent in 0..1_000_000u64, d1 in 0..100_000u64, d2 in 0..100_000u64,
now in 0..2_000_000u64, (lease, renew, retry) in valid_timing(),
) {
prop_assert!(timing_valid(lease, renew, retry));
let observed = sent + d1 + d2;
let believes = leading(now, Some(sent), renew, true);
let takes_over = decide(false, false, observed_age_ms(now, observed), lease) == Action::Acquire
&& now >= observed;
prop_assert!(!(believes && takes_over));
}
#[test]
fn effective_duration_matches_spec(rec: i32, fallback: u64) {
let r = effective_duration_ms(rec, fallback);
if rec > 0 {
prop_assert_eq!(r, (u64::from(rec.unsigned_abs()) * 1000).min(MAX_LEASE_MS));
} else {
prop_assert_eq!(r, fallback);
}
}
#[test]
fn transitions_match_spec(old: u32, me: bool) {
let r = next_transitions(old, me);
prop_assert_eq!(r, if me { old } else { old.saturating_add(1) });
}
#[test]
fn observation_matches_spec(prev: Option<u64>, changed: bool, now: u64, at: u64) {
let r = observed_at(prev, changed, now);
match prev {
Some(p) if !changed => prop_assert_eq!(r, p),
_ => prop_assert_eq!(r, now),
}
prop_assert_eq!(observed_age_ms(now, at), now.saturating_sub(at));
}
#[test]
fn grace_matches_spec(a: u64, b: u64, c: u64, d: u64) {
let sum = u128::from(a) + u128::from(b) + u128::from(c) + u128::from(d);
let r = grace_period_secs(a, b, c, d);
prop_assert_eq!(u128::from(r), sum.min(u128::from(u64::MAX)));
}
}
}