bitrouter_runtime/daemon/
mod.rs1mod pid;
2
3#[cfg(unix)]
4mod unix;
5#[cfg(windows)]
6mod windows;
7
8#[cfg(unix)]
9use unix as platform;
10#[cfg(windows)]
11use windows as platform;
12
13use pid::PidFile;
14
15use std::time::Duration;
16
17use crate::error::{Result, RuntimeError};
18use crate::paths::RuntimePaths;
19
20const STOP_TIMEOUT: Duration = Duration::from_secs(10);
21const STOP_POLL_INTERVAL: Duration = Duration::from_millis(100);
22
23pub struct DaemonManager {
26 paths: RuntimePaths,
27 pid_file: PidFile,
28}
29
30impl DaemonManager {
31 pub fn new(paths: RuntimePaths) -> Self {
32 let pid_file = PidFile::new(&paths.runtime_dir);
33 Self { paths, pid_file }
34 }
35
36 pub fn is_running(&self) -> Result<Option<u32>> {
39 if let Some(pid) = self.pid_file.read()? {
40 if platform::is_process_alive(pid) {
41 return Ok(Some(pid));
42 }
43 self.pid_file.remove()?;
45 }
46 Ok(None)
47 }
48
49 pub async fn start(&self) -> Result<u32> {
53 if let Some(pid) = self.is_running()? {
54 return Err(RuntimeError::Daemon(format!(
55 "daemon is already running (pid {pid})"
56 )));
57 }
58
59 let pid = platform::spawn_daemon(&self.paths)?;
60 self.pid_file.write(pid)?;
61
62 tokio::time::sleep(Duration::from_millis(200)).await;
64
65 if !platform::is_process_alive(pid) {
66 self.pid_file.remove()?;
67 return Err(RuntimeError::Daemon(
68 "daemon process exited immediately after start".into(),
69 ));
70 }
71
72 tracing::info!(pid, "daemon started");
73 Ok(pid)
74 }
75
76 pub async fn stop(&self) -> Result<()> {
79 let pid = match self.is_running()? {
80 Some(pid) => pid,
81 None => {
82 return Err(RuntimeError::Daemon("daemon is not running".into()));
83 }
84 };
85
86 platform::signal_stop(pid)?;
87
88 let deadline = tokio::time::Instant::now() + STOP_TIMEOUT;
90 loop {
91 if !platform::is_process_alive(pid) {
92 break;
93 }
94 if tokio::time::Instant::now() >= deadline {
95 tracing::warn!(pid, "daemon did not stop gracefully, force killing");
96 platform::signal_kill(pid)?;
97 tokio::time::sleep(Duration::from_millis(500)).await;
98 break;
99 }
100 tokio::time::sleep(STOP_POLL_INTERVAL).await;
101 }
102
103 self.pid_file.remove()?;
104 tracing::info!(pid, "daemon stopped");
105 Ok(())
106 }
107
108 pub async fn restart(&self) -> Result<u32> {
113 if self.is_running()?.is_some() {
114 self.stop().await?;
115 }
116 self.start().await
117 }
118}