Skip to main content

kunobi_daemon/
candidate.rs

1use std::{
2    io,
3    process::{Child, ExitStatus},
4};
5
6/// A newly spawned candidate is disposable until its selection commits.
7/// Dropping a selected candidate only reaps it; it never terminates the daemon.
8pub struct Candidate {
9    process: Option<Child>,
10    selected: bool,
11}
12impl Candidate {
13    /// Take ownership immediately after spawning a staged candidate.
14    pub fn new(process: Child) -> Self {
15        Self {
16            process: Some(process),
17            selected: false,
18        }
19    }
20    /// OS process identity used by the live verifier.
21    pub fn id(&self) -> u32 {
22        self.process.as_ref().expect("candidate owns child").id()
23    }
24    /// Observe whether the candidate exited before readiness.
25    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
26        self.process
27            .as_mut()
28            .expect("candidate owns child")
29            .try_wait()
30    }
31    /// Mark the selection boundary before doing repairable follow-up work.
32    pub fn selected(&mut self) {
33        self.selected = true;
34    }
35}
36impl Drop for Candidate {
37    fn drop(&mut self) {
38        let Some(mut process) = self.process.take() else {
39            return;
40        };
41        if process.try_wait().is_ok_and(|status| status.is_some()) {
42            return;
43        }
44        if !self.selected && process.kill().is_ok() {
45            let _ = process.wait();
46        } else {
47            let _ = std::thread::Builder::new()
48                .name("daemon-reaper".into())
49                .spawn(move || {
50                    let _ = process.wait();
51                });
52        }
53    }
54}