1use {
2 crate::*,
3 std::{
4 io::{
5 self,
6 BufRead,
7 BufReader,
8 },
9 process::{
10 Child,
11 Command,
12 },
13 thread,
14 time::Instant,
15 },
16 termimad::crossbeam::channel::{
17 self,
18 Receiver,
19 Sender,
20 },
21};
22
23pub struct MissionExecutor {
28 command_builder: CommandBuilder,
29 kill_command: Option<Vec<String>>,
30}
31
32pub struct TaskExecutor {
35 child_thread: thread::JoinHandle<()>,
37 stop_sender: Sender<StopMessage>,
38 grace_period_start: Option<Instant>, grace_period: Period,
40 _line_sender: Sender<CommandExecInfo>,
44 pub line_receiver: Receiver<CommandExecInfo>,
46}
47
48#[derive(Clone, Copy)]
50enum StopMessage {
51 SendStatus, Kill, }
54
55impl TaskExecutor {
56 pub fn interrupt(self) {
58 let _ = self.stop_sender.send(StopMessage::Kill);
59 }
60 pub fn die(self) {
62 if let Err(e) = self.stop_sender.send(StopMessage::Kill) {
63 debug!("failed to send 'die' signal: {e}");
64 }
65 if self.child_thread.join().is_err() {
66 warn!("child_thread.join() failed"); }
68 }
69 pub fn is_in_grace_period(&mut self) -> bool {
70 if let Some(grace_period_start) = self.grace_period_start {
71 if grace_period_start.elapsed() < self.grace_period.duration {
72 return true;
73 }
74 self.grace_period_start = None;
75 }
76 false
77 }
78}
79
80impl MissionExecutor {
81 pub fn new(mission: &Mission) -> anyhow::Result<Self> {
83 let command_builder = mission.get_command()?;
84 let kill_command = mission.kill_command();
85 Ok(Self {
86 command_builder,
87 kill_command,
88 })
89 }
90
91 pub fn start(
97 &mut self,
98 task: Task,
99 ) -> anyhow::Result<TaskExecutor> {
100 info!("start task {task:?}");
101 let grace_period = task.grace_period;
102 let grace_period_start = if grace_period.is_zero() {
103 None
104 } else {
105 Some(Instant::now())
106 };
107 let mut command_builder = self.command_builder.clone();
108 if let Some(backtrace) = task.backtrace {
109 command_builder.env("RUST_BACKTRACE", backtrace);
110 }
111 let kill_command = self.kill_command.clone();
112 let with_stdout = command_builder.is_with_stdout();
113 let (line_sender, line_receiver) = channel::unbounded();
114 let keepalive_sender = line_sender.clone();
115 let (stop_sender, stop_receiver) = channel::bounded(1);
116 let err_stop_sender = stop_sender.clone();
117
118 let child_thread = thread::spawn(move || {
120 if !grace_period.is_zero() {
124 thread::sleep(grace_period.duration);
125 }
126
127 let mut cmd = command_builder.build();
128 let mut child = match cmd.spawn() {
129 Ok(child) => child,
130 Err(e) => {
131 let _ = line_sender.send(CommandExecInfo::Error(
132 anyhow::anyhow!(e).context(format!("failed to spawn {cmd:?}")),
133 ));
134 return;
135 }
136 };
137
138 if with_stdout {
140 let sender = line_sender.clone();
141 let Some(stdout) = child.stdout.take() else {
142 warn!("process has no stdout"); return;
144 };
145 let mut buf_reader = BufReader::new(stdout);
146 thread::spawn(move || {
147 let mut line = String::new();
148 loop {
149 match buf_reader.read_line(&mut line) {
150 Err(e) => {
151 warn!("error : {e}");
152 }
153 Ok(0) => {
154 break;
156 }
157 Ok(_) => {
158 let response = CommandExecInfo::Line(RawCommandOutputLine {
159 content: line.clone(),
160 origin: CommandStream::StdOut,
161 });
162 if sender.send(response).is_err() {
163 break; }
165 }
166 }
167 line.clear();
168 }
169 });
170 }
171
172 let err_line_sender = line_sender.clone();
175 let stderr = child
178 .stderr
179 .take()
180 .expect("MissionExecutor requires piped stderr");
181 let mut buf_reader = BufReader::new(stderr);
182 thread::spawn(move || {
183 let mut line = String::new();
184 loop {
185 match buf_reader.read_line(&mut line) {
186 Err(e) => {
187 warn!("error : {e}");
188 }
189 Ok(0) => {
190 if let Err(e) = err_stop_sender.send(StopMessage::SendStatus) {
191 warn!("sending stop message failed: {e}");
192 }
193 break;
194 }
195 Ok(_) => {
196 let response = CommandExecInfo::Line(RawCommandOutputLine {
197 content: line.clone(),
198 origin: CommandStream::StdErr,
199 });
200 if err_line_sender.send(response).is_err() {
201 break; }
203 }
204 }
205 line.clear();
206 }
207 });
208
209 let natural_end = match stop_receiver.recv() {
211 Ok(StopMessage::SendStatus) => true, Ok(StopMessage::Kill) => {
213 debug!("explicit interrupt received");
214 kill(kill_command.as_deref(), &mut child);
215 false
216 }
217 Err(e) => {
218 debug!("recv error: {e}"); kill(kill_command.as_deref(), &mut child);
220 false
221 }
222 };
223 let status = child.wait();
226 if let Err(ref e) = status {
227 warn!("waiting for child failed: {e}");
228 }
229 if natural_end {
235 if let Ok(status) = status {
236 let _ = line_sender.send(CommandExecInfo::End { status });
237 }
238 }
239 });
240 Ok(TaskExecutor {
241 child_thread,
242 stop_sender,
243 grace_period_start,
244 grace_period,
245 _line_sender: keepalive_sender,
246 line_receiver,
247 })
248 }
249}
250
251fn kill(
255 kill_command: Option<&[String]>,
256 child: &mut Child,
257) {
258 if let Some(kill_command) = kill_command {
259 info!("launch specific kill command {kill_command:?}");
260 let Err(e) = run_kill_command(kill_command, child) else {
261 return;
262 };
263 warn!("specific kill command failed: {e}");
264 }
265 if let Err(e) = child.kill() {
266 warn!("failed to kill child process: {e}");
269 }
270}
271
272fn run_kill_command(
273 kill_command: &[String],
274 child: &mut Child,
275) -> io::Result<()> {
276 let (exe, args) = kill_command
277 .split_first()
278 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "empty kill command"))?;
279 let mut kill = Command::new(exe);
280 kill.args(args);
281 kill.arg(child.id().to_string());
282 let mut proc = kill.spawn()?;
283 let status = proc.wait()?;
284 if !status.success() {
285 return Err(io::Error::other(format!(
286 "kill command returned nonzero status: {status}"
287 )));
288 }
289 child.wait()?;
290 Ok(())
291}