use std::thread;
use std::time::{Duration, Instant};
use crate::error::CliError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WaitOutcome {
pub attempts: u32,
pub elapsed_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WaitPolicy {
pub timeout_ms: u64,
pub poll_ms: u64,
}
impl WaitPolicy {
pub fn new(timeout_ms: u64, poll_ms: u64) -> Self {
Self {
timeout_ms: timeout_ms.max(1),
poll_ms: poll_ms.max(1),
}
}
}
pub fn sleep_ms(ms: u64) {
if ms > 0 {
thread::sleep(Duration::from_millis(ms));
}
}
pub fn wait_until<F>(
condition_name: &str,
timeout_ms: u64,
poll_ms: u64,
mut check: F,
) -> Result<WaitOutcome, CliError>
where
F: FnMut() -> Result<bool, CliError>,
{
wait_until_with_policy(
condition_name,
WaitPolicy::new(timeout_ms, poll_ms),
&mut check,
)
}
pub fn wait_until_with_policy<F>(
condition_name: &str,
policy: WaitPolicy,
mut check: F,
) -> Result<WaitOutcome, CliError>
where
F: FnMut() -> Result<bool, CliError>,
{
let started = Instant::now();
let deadline = started + Duration::from_millis(policy.timeout_ms);
let mut attempts = 0u32;
loop {
attempts = attempts.saturating_add(1);
if check()? {
return Ok(WaitOutcome {
attempts,
elapsed_ms: started.elapsed().as_millis() as u64,
});
}
if Instant::now() >= deadline {
return Err(CliError::runtime(format!(
"timed out waiting for {condition_name} after {}ms",
policy.timeout_ms
)));
}
sleep_ms(policy.poll_ms);
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicU32, Ordering};
use super::wait_until;
#[test]
fn wait_until_succeeds_before_timeout() {
static ATTEMPTS: AtomicU32 = AtomicU32::new(0);
ATTEMPTS.store(0, Ordering::SeqCst);
let outcome = wait_until("ready", 200, 1, || {
let n = ATTEMPTS.fetch_add(1, Ordering::SeqCst);
Ok(n >= 2)
})
.expect("should succeed");
assert!(outcome.attempts >= 3);
assert!(outcome.elapsed_ms <= 200);
}
#[test]
fn wait_until_errors_on_timeout() {
let err = wait_until("never", 5, 1, || Ok(false)).expect_err("should timeout");
assert_eq!(err.exit_code(), 1);
assert!(err.to_string().contains("timed out waiting"));
}
}