mod pid;
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
#[cfg(unix)]
use unix as platform;
#[cfg(windows)]
use windows as platform;
use pid::PidFile;
use std::time::Duration;
use crate::runtime::error::{Result, RuntimeError};
use crate::runtime::paths::RuntimePaths;
const STOP_TIMEOUT: Duration = Duration::from_secs(10);
const STOP_POLL_INTERVAL: Duration = Duration::from_millis(100);
pub struct DaemonManager {
paths: RuntimePaths,
pid_file: PidFile,
}
impl DaemonManager {
pub fn new(paths: RuntimePaths) -> Self {
let pid_file = PidFile::new(&paths.runtime_dir);
Self { paths, pid_file }
}
pub fn is_running(&self) -> Result<Option<u32>> {
if let Some(pid) = self.pid_file.read()? {
if platform::is_process_alive(pid) {
return Ok(Some(pid));
}
self.pid_file.remove()?;
}
Ok(None)
}
pub async fn start(&self) -> Result<u32> {
if let Some(pid) = self.is_running()? {
return Err(RuntimeError::Daemon(format!(
"daemon is already running (pid {pid})"
)));
}
let pid = platform::spawn_daemon(&self.paths)?;
self.pid_file.write(pid)?;
tokio::time::sleep(Duration::from_millis(200)).await;
if !platform::is_process_alive(pid) {
self.pid_file.remove()?;
return Err(RuntimeError::Daemon(
"daemon process exited immediately after start".into(),
));
}
tracing::info!(pid, "daemon started");
Ok(pid)
}
pub async fn stop(&self) -> Result<()> {
let pid = match self.is_running()? {
Some(pid) => pid,
None => {
return Err(RuntimeError::Daemon("daemon is not running".into()));
}
};
platform::signal_stop(pid)?;
let deadline = tokio::time::Instant::now() + STOP_TIMEOUT;
loop {
if !platform::is_process_alive(pid) {
break;
}
if tokio::time::Instant::now() >= deadline {
tracing::warn!(pid, "daemon did not stop gracefully, force killing");
platform::signal_kill(pid)?;
tokio::time::sleep(Duration::from_millis(500)).await;
break;
}
tokio::time::sleep(STOP_POLL_INTERVAL).await;
}
self.pid_file.remove()?;
tracing::info!(pid, "daemon stopped");
Ok(())
}
pub async fn restart(&self) -> Result<u32> {
if self.is_running()?.is_some() {
self.stop().await?;
}
self.start().await
}
pub fn reload(&self) -> Result<()> {
let pid = match self.is_running()? {
Some(pid) => pid,
None => {
return Err(RuntimeError::Daemon("daemon is not running".into()));
}
};
#[cfg(unix)]
{
platform::signal_reload(pid)?;
}
#[cfg(windows)]
{
let _ = pid;
platform::signal_reload(&self.paths)?;
}
Ok(())
}
}