Skip to main content

unifier/daemon/
mod.rs

1//! Hot in-memory daemon lifecycle and client dispatch.
2
3#[cfg(unix)]
4mod client;
5#[cfg(unix)]
6mod notify;
7#[cfg(unix)]
8mod paths;
9#[cfg(unix)]
10mod protocol;
11#[cfg(unix)]
12mod server;
13
14#[cfg(unix)]
15pub use client::{
16    flush as client_flush, ping, read_pid, response_found, response_messages, response_ok,
17    response_uuid, response_value, shutdown, Client,
18};
19#[cfg(unix)]
20pub use notify::{subscribe, watch, EventHub, Notice};
21#[cfg(unix)]
22pub use protocol::{ok_empty, Request, Response};
23#[cfg(unix)]
24pub use paths::{daemon_dir, events_socket_path, pid_path, socket_path};
25#[cfg(unix)]
26pub use server::run as run_server;
27
28use std::process::{Command, Stdio};
29
30use crate::error::{Error, Result};
31use crate::home::UnifierHome;
32
33/// Start the daemon if it is not already running (Gradle-style invocation).
34pub fn ensure_running(home: &UnifierHome) -> Result<()> {
35    if is_running(home) {
36        return Ok(());
37    }
38    start(home, false)
39}
40
41/// Whether a hot daemon is running for this store root.
42pub fn is_running(home: &UnifierHome) -> bool {
43    #[cfg(unix)]
44    {
45        Client::is_running(home)
46    }
47    #[cfg(not(unix))]
48    {
49        let _ = home;
50        false
51    }
52}
53
54/// Start the hot daemon. With `foreground`, blocks in the current process (for tests).
55pub fn start(home: &UnifierHome, foreground: bool) -> Result<()> {
56    #[cfg(not(unix))]
57    {
58        let _ = (home, foreground);
59        return Err(Error::msg("hot daemon requires a Unix platform"));
60    }
61
62    #[cfg(unix)]
63    {
64        if Client::is_running(home) {
65            return Err(Error::msg("daemon is already running"));
66        }
67
68        home.ensure()?;
69        std::fs::create_dir_all(crate::daemon::paths::daemon_dir(home))?;
70
71        if foreground {
72            return run_server(home.clone());
73        }
74
75        let exe = std::env::current_exe()?;
76        let mut cmd = Command::new(exe);
77        cmd.arg("daemon").arg("run");
78        if let Some(p) = home.global_path().to_str() {
79            cmd.args(["--home", p]);
80        }
81        if let Some(name) = home.chroot_name() {
82            cmd.args(["--chroot", name]);
83        }
84        cmd.stdin(Stdio::null())
85            .stdout(Stdio::null())
86            .stderr(Stdio::null());
87
88        let child = cmd.spawn()?;
89        wait_for_socket(home, child.id())?;
90        Ok(())
91    }
92}
93
94/// Stop the daemon, flushing dirty state first.
95pub fn stop(home: &UnifierHome) -> Result<()> {
96    #[cfg(not(unix))]
97    {
98        let _ = home;
99        return Err(Error::msg("hot daemon requires a Unix platform"));
100    }
101
102    #[cfg(unix)]
103    {
104        shutdown(home)
105    }
106}
107
108/// Print daemon status to stdout.
109pub fn status(home: &UnifierHome) -> Result<()> {
110    #[cfg(not(unix))]
111    {
112        let _ = home;
113        println!("daemon: unavailable (requires Unix)");
114        return Ok(());
115    }
116
117    #[cfg(unix)]
118    {
119        if Client::is_running(home) {
120            let pid = read_pid(home)?.unwrap_or(0);
121            println!("daemon: running (pid {pid})");
122            println!("socket: {}", socket_path(home).display());
123            println!("events: {}", events_socket_path(home).display());
124        } else {
125            println!("daemon: stopped");
126        }
127        Ok(())
128    }
129}
130
131/// Flush dirty in-memory state to disk via the running daemon.
132pub fn flush(home: &UnifierHome) -> Result<()> {
133    #[cfg(not(unix))]
134    {
135        let _ = home;
136        return Err(Error::msg("hot daemon requires a Unix platform"));
137    }
138
139    #[cfg(unix)]
140    {
141        let dirty = client_flush(home)?;
142        if dirty {
143            println!("flushed dirty state to disk");
144        } else {
145            println!("nothing to flush");
146        }
147        Ok(())
148    }
149}
150
151#[cfg(unix)]
152fn wait_for_socket(home: &UnifierHome, _pid: u32) -> Result<()> {
153    let path = socket_path(home);
154    for _ in 0..100 {
155        if path.exists() && Client::is_running(home) {
156            return ping(home);
157        }
158        std::thread::sleep(std::time::Duration::from_millis(50));
159    }
160    Err(Error::msg("daemon failed to start"))
161}