use anyhow::Result;
use std::process::Command;
pub async fn prepare_port(port: u16) -> Result<()> {
tracing::debug!(port = port, "Preparing port");
if is_port_in_use(port) {
tracing::debug!(port = port, "Port is in use, cleaning up");
cleanup_port_processes(port).await?;
}
tracing::debug!(port = port, "Port is ready");
Ok(())
}
pub fn is_port_in_use(port: u16) -> bool {
std::net::TcpStream::connect(format!("127.0.0.1:{port}")).is_ok()
}
pub fn is_port_responsive(port: u16) -> bool {
is_port_in_use(port)
}
pub async fn cleanup_port_processes(port: u16) -> Result<()> {
let output = Command::new("lsof")
.args(["-ti", &format!(":{port}")])
.output()?;
if !output.stdout.is_empty() {
let pids = String::from_utf8_lossy(&output.stdout);
for pid in pids.lines() {
if !pid.is_empty() {
tracing::debug!(port = port, pid = %pid, "Stopping process on port");
let _ = Command::new("kill").args(["-15", pid]).output();
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let _ = Command::new("kill").args(["-9", pid]).output();
}
}
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
}
Ok(())
}
pub async fn wait_for_port_release(port: u16) -> Result<()> {
let max_attempts = 10;
let delay = std::time::Duration::from_millis(100);
for attempt in 1..=max_attempts {
if !is_port_in_use(port) {
return Ok(());
}
if attempt < max_attempts {
tokio::time::sleep(delay).await;
}
}
Err(anyhow::anyhow!(
"Port {port} did not become available after {max_attempts} attempts"
))
}
pub const fn cleanup_port_resources(_port: u16) {}
pub fn find_available_port(start_port: u16, end_port: u16) -> Result<u16> {
for port in start_port..=end_port {
if !is_port_in_use(port) {
return Ok(port);
}
}
Err(anyhow::anyhow!(
"No available ports in range {start_port}-{end_port}"
))
}