use std::net::{SocketAddr, TcpStream};
use std::time::{Duration, Instant};
#[derive(Debug, thiserror::Error)]
pub enum ProbeError {
#[error("gateway did not become ready within {elapsed:?}")]
Timeout {
elapsed: Duration,
},
}
pub fn probe_tcp(addr: SocketAddr, connect_timeout: Duration) -> bool {
TcpStream::connect_timeout(&addr, connect_timeout).is_ok()
}
pub fn wait_for_ready(addr: SocketAddr, overall_timeout: Duration, poll_interval: Duration) -> Result<(), ProbeError> {
let start = Instant::now();
loop {
if probe_tcp(addr, poll_interval) {
return Ok(());
}
if start.elapsed() >= overall_timeout {
return Err(ProbeError::Timeout {
elapsed: start.elapsed(),
});
}
std::thread::sleep(poll_interval);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::TcpListener;
#[test]
fn probe_tcp_returns_true_against_open_listener() {
let _net = crate::test_support::net_guard();
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
assert!(probe_tcp(addr, Duration::from_millis(200)));
}
#[test]
fn probe_tcp_returns_false_when_nothing_is_listening() {
let addr: SocketAddr = "127.0.0.1:1".parse().unwrap();
assert!(!probe_tcp(addr, Duration::from_millis(200)));
}
#[test]
fn wait_for_ready_returns_ok_when_listener_is_already_up() {
let _net = crate::test_support::net_guard();
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
wait_for_ready(addr, Duration::from_millis(500), Duration::from_millis(50))
.expect("wait_for_ready should succeed when listener is up");
}
#[test]
fn wait_for_ready_returns_timeout_when_nothing_listens() {
let addr: SocketAddr = "127.0.0.1:1".parse().unwrap();
let budget = Duration::from_millis(200);
let result = wait_for_ready(addr, budget, Duration::from_millis(50));
match result {
Err(ProbeError::Timeout { elapsed }) => {
assert!(
elapsed >= budget,
"elapsed {elapsed:?} should not be shorter than the budget {budget:?}",
);
}
other => panic!("expected Timeout error, got {other:?}"),
}
}
#[test]
fn wait_for_ready_returns_ok_when_listener_appears_mid_wait() {
let _net = crate::test_support::net_guard();
let probe = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = probe.local_addr().unwrap();
drop(probe);
let helper = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(150));
let _held = TcpListener::bind(addr).expect("re-bind ephemeral port");
std::thread::sleep(Duration::from_millis(500));
});
wait_for_ready(addr, Duration::from_secs(2), Duration::from_millis(50))
.expect("listener should be detected once it appears mid-wait");
helper.join().unwrap();
}
}