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