use tokio::net::UnixStream;
pub async fn ensure_server_running() -> anyhow::Result<()> {
let path = crate::server::socket_path()?;
if UnixStream::connect(&path).await.is_ok() {
return Ok(());
}
let lock_path = crate::server::socket::lock_path()?;
let lock_file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(&lock_path)?;
let _lock_guard = match nix::fcntl::Flock::lock(lock_file, nix::fcntl::FlockArg::LockExclusiveNonblock) {
Ok(guard) => guard, Err((_, nix::errno::Errno::EWOULDBLOCK)) => {
for _ in 0..50 {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
if UnixStream::connect(&path).await.is_ok() {
return Ok(());
}
}
anyhow::bail!("timed out waiting for another client to start server");
}
Err((_, e)) => anyhow::bail!("failed to acquire startup lock: {}", e),
};
if UnixStream::connect(&path).await.is_ok() {
return Ok(());
}
let exe = std::env::current_exe()?;
let log_path = path.with_file_name("retach.log");
let log_file_stderr = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)?;
use std::os::unix::process::CommandExt;
unsafe {
let mut child = std::process::Command::new(exe)
.arg("server")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::from(log_file_stderr))
.pre_exec(|| {
if nix::libc::setsid() == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(())
})
.spawn()?;
std::thread::spawn(move || { let _ = child.wait(); });
}
for _ in 0..50 {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
if UnixStream::connect(&path).await.is_ok() {
return Ok(());
}
}
anyhow::bail!("failed to start server");
}