rightkit_daemon/
incumbent.rs1use crate::record::ServiceRecord;
15use rightkit_child_process::AdoptedIdentity;
16use std::io;
17use std::num::NonZeroU32;
18use std::time::Duration;
19
20const START_TOLERANCE: Duration = Duration::from_secs(2);
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26#[non_exhaustive]
27pub enum IncumbentStop {
28 AlreadyGone,
30 Asked,
32 Signalled,
34 Forced,
36}
37
38#[derive(Debug)]
41#[non_exhaustive]
42pub enum IncumbentError {
43 NotIncumbent { pid: u32 },
45 NoIdentity { pid: u32 },
48 OwnProcess,
50 Survived { pid: u32 },
52 Io(io::Error),
54}
55
56impl std::fmt::Display for IncumbentError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 Self::NotIncumbent { pid } => {
60 write!(
61 f,
62 "pid {pid} is not the recorded host (recycled); not signalled"
63 )
64 }
65 Self::NoIdentity { pid } => write!(
66 f,
67 "the record of pid {pid} has no start time, so it was asked but never signalled"
68 ),
69 Self::OwnProcess => write!(f, "the record names this process"),
70 Self::Survived { pid } => write!(f, "pid {pid} survived the forced stop"),
71 Self::Io(e) => write!(f, "{e}"),
72 }
73 }
74}
75
76impl std::error::Error for IncumbentError {}
77
78impl From<io::Error> for IncumbentError {
79 fn from(e: io::Error) -> Self {
80 Self::Io(e)
81 }
82}
83
84pub fn stop_incumbent(
88 record: &ServiceRecord,
89 grace: Duration,
90 ask: impl FnOnce() -> io::Result<()>,
91) -> Result<IncumbentStop, IncumbentError> {
92 let pid = record.pid;
93 if pid == std::process::id() {
94 return Err(IncumbentError::OwnProcess);
95 }
96 let Some(nz) = NonZeroU32::new(pid) else {
97 return Ok(IncumbentStop::AlreadyGone);
98 };
99 if !crate::transport::pid_alive(pid) {
100 return Ok(IncumbentStop::AlreadyGone);
101 }
102 if record.process_started_ms == 0 {
103 let _ = ask();
105 let deadline = std::time::Instant::now() + grace;
106 while std::time::Instant::now() < deadline {
107 if !crate::transport::pid_alive(pid) {
108 return Ok(IncumbentStop::Asked);
109 }
110 std::thread::sleep(Duration::from_millis(25));
111 }
112 return Err(IncumbentError::NoIdentity { pid });
113 }
114 let identity =
115 AdoptedIdentity::from_recorded_unix_ms(nz, record.process_started_ms, START_TOLERANCE)?;
116 let Some(identity) = identity else {
117 return if crate::transport::pid_alive(pid) {
119 Err(IncumbentError::NotIncumbent { pid })
120 } else {
121 Ok(IncumbentStop::AlreadyGone)
122 };
123 };
124 let _ = ask();
125 if identity.wait_confirmed_gone(grace)? {
126 return Ok(IncumbentStop::Asked);
127 }
128 if identity.quit_gracefully(grace)? {
129 return Ok(IncumbentStop::Signalled);
130 }
131 let report = identity.terminate_tree(grace)?;
132 if report.survivors.is_empty() && report.failed.is_empty() && identity.confirmed_gone()? {
133 Ok(IncumbentStop::Forced)
134 } else {
135 Err(IncumbentError::Survived { pid })
136 }
137}