hyperdb_mcp/daemon/
spawn.rs1use std::io;
11use std::process::Command;
12use std::time::{Duration, Instant};
13
14use tracing::{debug, info};
15
16use super::discovery::{self, DaemonInfo};
17
18const SPAWN_TIMEOUT: Duration = Duration::from_secs(10);
20
21const POLL_INTERVAL: Duration = Duration::from_millis(100);
23
24pub fn ensure_daemon(port: u16) -> io::Result<DaemonInfo> {
31 if let Some(info) = discovery::discover() {
33 debug!(endpoint = %info.hyperd_endpoint, "daemon already running");
34 return Ok(info);
35 }
36
37 info!("no running daemon detected, spawning one");
38 spawn_detached(port)?;
39 wait_for_daemon()
40}
41
42fn spawn_detached(port: u16) -> io::Result<()> {
44 let exe = std::env::current_exe()?;
45 let port_str = port.to_string();
46
47 let mut cmd = Command::new(&exe);
48 cmd.arg("daemon").arg("--port").arg(&port_str);
49
50 cmd.stdin(std::process::Stdio::null());
52 cmd.stdout(std::process::Stdio::null());
53 cmd.stderr(std::process::Stdio::null());
54
55 #[cfg(unix)]
57 {
58 use std::os::unix::process::CommandExt;
59 unsafe {
63 cmd.pre_exec(|| {
64 libc::setsid();
65 Ok(())
66 });
67 }
68 }
69
70 #[cfg(windows)]
71 {
72 use std::os::windows::process::CommandExt;
73 const CREATE_NO_WINDOW: u32 = 0x0800_0000;
74 const DETACHED_PROCESS: u32 = 0x0000_0008;
75 cmd.creation_flags(CREATE_NO_WINDOW | DETACHED_PROCESS);
76 }
77
78 let child = cmd.spawn()?;
79 info!(pid = child.id(), "daemon process spawned");
80 Ok(())
81}
82
83fn wait_for_daemon() -> io::Result<DaemonInfo> {
85 let start = Instant::now();
86 loop {
87 if let Some(info) = discovery::discover() {
88 info!(endpoint = %info.hyperd_endpoint, "daemon is ready");
89 return Ok(info);
90 }
91
92 if start.elapsed() >= SPAWN_TIMEOUT {
93 return Err(io::Error::new(
94 io::ErrorKind::TimedOut,
95 format!(
96 "daemon did not become ready within {} seconds",
97 SPAWN_TIMEOUT.as_secs()
98 ),
99 ));
100 }
101
102 std::thread::sleep(POLL_INTERVAL);
103 }
104}