use tokio::net::UnixStream;
const SERVER_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100);
const SERVER_POLL_MAX: usize = 50;
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..SERVER_POLL_MAX {
tokio::time::sleep(SERVER_POLL_INTERVAL).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());
}
let max_fd = match nix::libc::sysconf(nix::libc::_SC_OPEN_MAX) {
n if n > 0 => (n as i32).min(4096),
_ => 1024,
};
for fd in 3..max_fd {
nix::libc::fcntl(fd, nix::libc::F_SETFD, nix::libc::FD_CLOEXEC);
}
Ok(())
})
.spawn()?;
std::thread::spawn(move || {
let _ = child.wait();
});
}
for _ in 0..SERVER_POLL_MAX {
tokio::time::sleep(SERVER_POLL_INTERVAL).await;
if UnixStream::connect(&path).await.is_ok() {
return Ok(());
}
}
anyhow::bail!("failed to start server");
}