obnam_benchmark/
daemon.rs1use 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#[derive(Debug, thiserror::Error)]
16pub enum DaemonError {
17 #[error("daemon took longer than {0} ms to start: {1}\n{2}")]
20 Timeout(u128, String, String),
21
22 #[error(transparent)]
24 TempFile(#[from] std::io::Error),
25
26 #[error("failed to read daemon stdout: {0}")]
28 Stdout(std::io::Error),
29
30 #[error("failed to read daemon stderr: {0}")]
32 Stderr(std::io::Error),
33
34 #[error("failed to kill process {0}: {1}")]
36 Kill(i32, nix::Error),
37}
38
39pub 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 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn timeout(mut self, millis: u64) -> Self {
66 self.timeout = Duration::from_millis(millis);
67 self
68 }
69
70 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 if let Ok(pid) = std::fs::read(pid.path()) {
111 let pid = String::from_utf8_lossy(&pid).into_owned();
113 if let Some(pid) = pid.strip_suffix('\n') {
115 if let Ok(pid) = pid.parse() {
117 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#[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 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 pub fn stdout(&self) -> Result<Vec<u8>, DaemonError> {
184 std::fs::read(&self.stdout).map_err(DaemonError::Stdout)
185 }
186
187 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 }
198 }
199}
200
201fn sleep_briefly() {
202 sleep(Duration::from_millis(100));
203}