kunobi_daemon/
candidate.rs1use std::{
2 io,
3 ops::{Deref, DerefMut},
4 process::{Child, ExitStatus},
5};
6
7#[cfg(feature = "local")]
8use crate::local::ProcessHandle;
9
10pub struct Candidate {
13 process: Process,
14 selected: bool,
15}
16enum Process {
17 Child {
18 child: Reaped,
19 #[cfg(feature = "local")]
20 exit: Option<ProcessHandle>,
21 },
22 #[cfg(feature = "launch")]
23 Daemon(crate::launch::DaemonChild),
24}
25impl Candidate {
26 pub fn new(process: Child) -> Self {
32 Self {
33 process: Process::Child {
34 #[cfg(feature = "local")]
35 exit: ProcessHandle::for_child(&process).ok(),
36 child: Reaped::new(process),
37 },
38 selected: false,
39 }
40 }
41 pub fn id(&self) -> crate::ProcessId {
43 match &self.process {
44 Process::Child { child, .. } => crate::ProcessId::of(child),
45 #[cfg(feature = "launch")]
46 Process::Daemon(daemon) => daemon.id(),
47 }
48 }
49 pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
51 match &mut self.process {
52 Process::Child { child, .. } => child.try_wait(),
53 #[cfg(feature = "launch")]
54 Process::Daemon(daemon) => daemon.try_wait(),
55 }
56 }
57 #[cfg(feature = "local")]
62 pub fn exit_handle(&mut self) -> io::Result<&mut ProcessHandle> {
63 match &mut self.process {
64 Process::Child { exit, .. } => exit
65 .as_mut()
66 .ok_or_else(|| io::Error::other("the candidate's exit handle could not be opened")),
67 #[cfg(feature = "launch")]
68 Process::Daemon(daemon) => Ok(daemon.exit_handle()),
69 }
70 }
71 #[cfg(feature = "local")]
74 pub fn wait_until(&mut self, deadline: std::time::Instant) -> io::Result<Option<ExitStatus>> {
75 match &mut self.process {
76 Process::Child { child, exit } => {
77 if !crate::local::wait_child(|| child.try_wait(), exit.as_mut(), deadline)? {
78 return Ok(None);
79 }
80 child.wait().map(Some)
81 }
82 #[cfg(feature = "launch")]
83 Process::Daemon(daemon) => daemon.wait_until(deadline),
84 }
85 }
86 pub fn selected(&mut self) {
88 self.selected = true;
89 }
90 fn stop(&mut self) -> io::Result<ExitStatus> {
92 match &mut self.process {
93 Process::Child { child, .. } => {
94 child.kill()?;
95 child.wait()
96 }
97 #[cfg(feature = "launch")]
98 Process::Daemon(daemon) => {
99 daemon.kill()?;
100 daemon.wait()
101 }
102 }
103 }
104}
105#[cfg(feature = "launch")]
109impl From<crate::launch::DaemonChild> for Candidate {
110 fn from(daemon: crate::launch::DaemonChild) -> Self {
111 Self {
112 process: Process::Daemon(daemon),
113 selected: false,
114 }
115 }
116}
117impl Drop for Candidate {
118 fn drop(&mut self) {
120 if !self.selected {
121 let _ = self.stop();
122 }
123 }
124}
125
126#[derive(Debug)]
131pub(crate) struct Reaped(Option<Child>);
132impl Reaped {
133 pub(crate) fn new(child: Child) -> Self {
134 Self(Some(child))
135 }
136}
137impl Deref for Reaped {
138 type Target = Child;
139 fn deref(&self) -> &Child {
140 self.0
141 .as_ref()
142 .expect("the child is taken only when dropped")
143 }
144}
145impl DerefMut for Reaped {
146 fn deref_mut(&mut self) -> &mut Child {
147 self.0
148 .as_mut()
149 .expect("the child is taken only when dropped")
150 }
151}
152impl Drop for Reaped {
153 fn drop(&mut self) {
154 let Some(mut child) = self.0.take() else {
155 return;
156 };
157 if child.try_wait().is_ok_and(|status| status.is_some()) {
158 return;
159 }
160 let _ = std::thread::Builder::new()
163 .name("daemon-reaper".into())
164 .spawn(move || {
165 let _ = child.wait();
166 });
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 #[cfg(feature = "local")]
173 #[test]
174 fn a_candidate_watches_its_process_from_the_moment_it_is_taken() {
175 let child = std::process::Command::new(std::env::current_exe().unwrap())
178 .arg("--list")
179 .stdout(std::process::Stdio::null())
180 .spawn()
181 .unwrap();
182 let pid = crate::ProcessId::of(&child);
183 let mut candidate = super::Candidate::new(child);
184 assert_eq!(candidate.exit_handle().unwrap().pid(), pid);
185 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
186 assert!(candidate.wait_until(deadline).unwrap().unwrap().success());
187 }
188}