obnam_benchmark/
daemon.rs

1use log::{debug, error, info};
2use nix::sys::signal::kill;
3use nix::sys::signal::Signal;
4use nix::unistd::Pid;
5use std::ffi::OsStr;
6use std::fs::read;
7use std::os::unix::ffi::OsStrExt;
8use std::path::{Path, PathBuf};
9use std::process::Command;
10use std::thread::sleep;
11use std::time::{Duration, Instant};
12use tempfile::NamedTempFile;
13
14/// Possible errors from starting and stopping daemons.
15#[derive(Debug, thiserror::Error)]
16pub enum DaemonError {
17    /// The daemon took too long to start. The timeout can be
18    /// configured with [DaemonManager::timeout].
19    #[error("daemon took longer than {0} ms to start: {1}\n{2}")]
20    Timeout(u128, String, String),
21
22    /// Something went wrong, when handling temporary files.
23    #[error(transparent)]
24    TempFile(#[from] std::io::Error),
25
26    /// Something went wrong read standard output of daemon.
27    #[error("failed to read daemon stdout: {0}")]
28    Stdout(std::io::Error),
29
30    /// Something went wrong read error output of daemon.
31    #[error("failed to read daemon stderr: {0}")]
32    Stderr(std::io::Error),
33
34    /// Failed to kill a daemon.
35    #[error("failed to kill process {0}: {1}")]
36    Kill(i32, nix::Error),
37}
38
39/// Manage daemons.
40///
41/// A daemon is a process running in the background, doing useful
42/// things. For Obnam benchmarks, it's the Obnam server, but this is a
43/// generic manager. This version requires the `daemonize` helper
44/// program to be available on $PATH.
45pub struct DaemonManager {
46    timeout: Duration,
47}
48
49impl Default for DaemonManager {
50    fn default() -> Self {
51        Self {
52            timeout: Duration::from_millis(1000),
53        }
54    }
55}
56
57impl DaemonManager {
58    /// Create a new manager instance, with default settings.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Set the timeout for waiting on a daemon to start, in
64    /// milliseconds.
65    pub fn timeout(mut self, millis: u64) -> Self {
66        self.timeout = Duration::from_millis(millis);
67        self
68    }
69
70    /// Start a daemon.
71    ///
72    /// The daemon is considered started if its process id (PID) is
73    /// known. The daemon may take longer to actually be in a useful
74    /// state, and it may fail after the PID is known, for example if
75    /// it reads a configuration file and that has errors. This
76    /// function won't wait for that to happen: it only cares about
77    /// the PID.
78    pub fn start(
79        &self,
80        argv: &[&OsStr],
81        stdout: &Path,
82        stderr: &Path,
83    ) -> Result<Daemon, DaemonError> {
84        info!("start daemon: {:?}", argv);
85        let pid = NamedTempFile::new()?;
86        let output = Command::new("daemonize")
87            .args(&[
88                "-c",
89                "/",
90                "-e",
91                &stderr.display().to_string(),
92                "-o",
93                &stdout.display().to_string(),
94                "-p",
95                &pid.path().display().to_string(),
96            ])
97            .args(argv)
98            .output()
99            .unwrap();
100        if output.status.code() != Some(0) {
101            eprintln!("{}", String::from_utf8_lossy(&output.stdout));
102            eprintln!("{}", String::from_utf8_lossy(&output.stderr));
103            std::process::exit(1);
104        }
105
106        debug!("waiting for daemon to write PID file");
107        let time = Instant::now();
108        while time.elapsed() < self.timeout {
109            // Do we have the pid file?
110            if let Ok(pid) = std::fs::read(pid.path()) {
111                // Parse it as a string. We don't mind if it's not purely UTF8.
112                let pid = String::from_utf8_lossy(&pid).into_owned();
113                // Strip newline, if any.
114                if let Some(pid) = pid.strip_suffix('\n') {
115                    // Parse as an integer, if possible.
116                    if let Ok(pid) = pid.parse() {
117                        // We have a pid, stop waiting.
118                        info!("got pid for daemon: pid");
119                        return Ok(Daemon::new(pid, stdout, stderr));
120                    }
121                }
122                sleep_briefly();
123            } else {
124                sleep_briefly();
125            }
126        }
127
128        error!(
129            "no PID file within {} ms, giving up",
130            self.timeout.as_millis()
131        );
132        let mut cmd = String::new();
133        for arg in argv {
134            if !cmd.is_empty() {
135                cmd.push(' ');
136            }
137            cmd.push_str(
138                &String::from_utf8_lossy(arg.as_bytes())
139                    .to_owned()
140                    .to_string(),
141            );
142        }
143        let err = read(&stderr).map_err(DaemonError::Stderr)?;
144        let err = String::from_utf8_lossy(&err).into_owned();
145        Err(DaemonError::Timeout(self.timeout.as_millis(), cmd, err))
146    }
147}
148
149/// A running daemon.
150///
151/// The daemon process is killed, when the `Daemon` struct is dropped.
152#[derive(Debug)]
153pub struct Daemon {
154    pid: Option<i32>,
155    stdout: PathBuf,
156    stderr: PathBuf,
157}
158
159impl Daemon {
160    fn new(pid: i32, stdout: &Path, stderr: &Path) -> Self {
161        info!("started daemon with PID {}", pid);
162        Self {
163            pid: Some(pid),
164            stdout: stdout.to_path_buf(),
165            stderr: stderr.to_path_buf(),
166        }
167    }
168
169    /// Explicitly stop a daemon.
170    ///
171    /// Calling this function is only useful if you want to handle
172    /// errors. It can only be called once.
173    pub fn stop(&mut self) -> Result<(), DaemonError> {
174        if let Some(raw_pid) = self.pid.take() {
175            info!("stopping daemon with PID {}", raw_pid);
176            let pid = Pid::from_raw(raw_pid);
177            kill(pid, Some(Signal::SIGKILL)).map_err(|e| DaemonError::Kill(raw_pid, e))?;
178        }
179        Ok(())
180    }
181
182    /// Return what the daemon has written to its stderr so far.
183    pub fn stdout(&self) -> Result<Vec<u8>, DaemonError> {
184        std::fs::read(&self.stdout).map_err(DaemonError::Stdout)
185    }
186
187    /// Return what the daemon has written to its stderr so far.
188    pub fn stderr(&self) -> Result<Vec<u8>, DaemonError> {
189        std::fs::read(&self.stderr).map_err(DaemonError::Stderr)
190    }
191}
192
193impl Drop for Daemon {
194    fn drop(&mut self) {
195        if self.stop().is_err() {
196            // Do nothing.
197        }
198    }
199}
200
201fn sleep_briefly() {
202    sleep(Duration::from_millis(100));
203}