use std::future::Future;
use std::time::Duration;
use anyhow::Result;
use super::config::ResolvedConfig;
use super::extend::extend_lease;
use super::lease_create::parse_cli_duration;
const MIN_INTERVAL: Duration = Duration::from_secs(15);
const DEFAULT_INTERVAL: Duration = Duration::from_secs(1800);
pub(crate) fn heartbeat_interval(ttl: &str) -> Duration {
parse_cli_duration(ttl)
.map(|d| d / 2)
.unwrap_or(DEFAULT_INTERVAL)
.max(MIN_INTERVAL)
}
pub(crate) async fn heartbeat_until<F: Future<Output = ()>>(
config: &ResolvedConfig,
lease_id: &str,
ttl: &str,
stop: F,
verbose: bool,
) -> Result<()> {
let interval = heartbeat_interval(ttl);
tokio::pin!(stop);
loop {
tokio::select! {
_ = &mut stop => break,
_ = tokio::time::sleep(interval) => {
match extend_lease(config, lease_id, ttl).await {
Ok(expires_at) => {
if verbose {
eprintln!("Heartbeat: extended {lease_id} (expires {expires_at})");
}
}
Err(e) => {
eprintln!("Heartbeat stopped for {lease_id}: {e}");
break;
}
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interval_is_half_ttl_floored_at_min() {
assert_eq!(heartbeat_interval("1h"), Duration::from_secs(1800));
assert_eq!(heartbeat_interval("30m"), Duration::from_secs(900));
assert_eq!(heartbeat_interval("10s"), Duration::from_secs(15));
assert_eq!(
heartbeat_interval("not-a-duration"),
Duration::from_secs(1800)
);
}
}