Actually fix process cleanup

This commit is contained in:
elkowar 2020-10-24 22:48:19 +02:00
parent 3849835ff9
commit 412b53aca6
3 changed files with 71 additions and 32 deletions

11
Cargo.lock generated
View file

@ -339,6 +339,7 @@ dependencies = [
"roxmltree", "roxmltree",
"scheduled-executor", "scheduled-executor",
"serde", "serde",
"simple-signal",
"smart-default", "smart-default",
"stoppable_thread", "stoppable_thread",
"structopt", "structopt",
@ -1634,6 +1635,16 @@ dependencies = [
"syn 1.0.44", "syn 1.0.44",
] ]
[[package]]
name = "simple-signal"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53f7da44adcc42667d57483bd93f81295f27d66897804b757573b61b6f13288b"
dependencies = [
"lazy_static",
"libc",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.3" version = "0.3.3"

View file

@ -46,6 +46,7 @@ nix = "0.19"
smart-default = "0.6" smart-default = "0.6"
filedescriptor = "0.7" filedescriptor = "0.7"
ctrlc = { version = "3.1", features = [ "termination" ] } ctrlc = { version = "3.1", features = [ "termination" ] }
simple-signal = "1.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View file

@ -124,7 +124,9 @@ impl ScriptVarHandler {
}; };
util::print_result_err("in script-var tail handler thread", &result); util::print_result_err("in script-var tail handler thread", &result);
} }
script_var_processes.values().for_each(|process| process.kill()); for process in script_var_processes.values() {
util::print_result_err("While killing tail-var process at the end of tail task", &process.kill());
}
}); });
self.tail_handler_thread = Some(thread_handle); self.tail_handler_thread = Some(thread_handle);
Ok(()) Ok(())
@ -143,7 +145,7 @@ pub mod script_var_process {
sys::{signal, wait}, sys::{signal, wait},
unistd::Pid, unistd::Pid,
}; };
use std::{io::BufReader, process::Stdio, sync::Mutex}; use std::{ffi::CString, io::BufReader, sync::Mutex};
use crate::util; use crate::util;
@ -151,54 +153,79 @@ pub mod script_var_process {
static ref SCRIPT_VAR_CHILDREN: Mutex<Vec<u32>> = Mutex::new(Vec::new()); static ref SCRIPT_VAR_CHILDREN: Mutex<Vec<u32>> = Mutex::new(Vec::new());
} }
fn terminate_pid(pid: u32) { fn terminate_pid(pid: u32) -> Result<()> {
println!("Killing pid: {}", pid); signal::kill(Pid::from_raw(pid as i32), signal::SIGTERM)?;
let result = signal::kill(Pid::from_raw(pid as i32), signal::SIGTERM); wait::waitpid(Pid::from_raw(pid as i32), None)?;
util::print_result_err("While killing tail-var child processes", &result); Ok(())
let wait_result = wait::waitpid(Pid::from_raw(pid as i32), None);
util::print_result_err("While killing tail-var child processes", &wait_result);
} }
/// This function should be called in the signal handler, killing all child processes. /// This function should be called in the signal handler, killing all child processes.
pub fn on_application_death() { pub fn on_application_death() {
SCRIPT_VAR_CHILDREN SCRIPT_VAR_CHILDREN.lock().unwrap().drain(..).for_each(|pid| {
.lock() let result = terminate_pid(pid);
.unwrap() util::print_result_err("While killing process '{}' during cleanup", &result);
.drain(..) });
.for_each(|pid| terminate_pid(pid));
} }
pub struct ScriptVarProcess { pub struct ScriptVarProcess {
child: std::process::Child, pid: i32,
pub stdout_reader: BufReader<std::process::ChildStdout>, pub stdout_reader: BufReader<filedescriptor::FileDescriptor>,
} }
impl ScriptVarProcess { impl ScriptVarProcess {
pub(super) fn run(command: &str) -> Result<Self> { pub(super) fn run(command: &str) -> Result<Self> {
println!("Running {}", command); use nix::unistd::*;
let mut child = std::process::Command::new("/bin/sh")
.arg("-c") let pipe = filedescriptor::Pipe::new()?;
.arg(command)
.stdout(Stdio::piped()) match unsafe { fork()? } {
.stderr(Stdio::inherit()) ForkResult::Parent { child, .. } => {
.stdin(Stdio::null()) SCRIPT_VAR_CHILDREN.lock().unwrap().push(child.as_raw() as u32);
.spawn()?;
SCRIPT_VAR_CHILDREN.lock().unwrap().push(child.id());
Ok(ScriptVarProcess { Ok(ScriptVarProcess {
stdout_reader: BufReader::new(child.stdout.take().unwrap()), stdout_reader: BufReader::new(pipe.read),
child, pid: child.as_raw(),
}) })
} }
ForkResult::Child => {
let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0));
match unsafe { fork()? } {
ForkResult::Parent { .. } => {
simple_signal::set_handler(&[simple_signal::Signal::Int, simple_signal::Signal::Term], |_| {
let pgid = getpgid(Some(getpid())).unwrap();
let _ = signal::killpg(pgid, nix::sys::signal::SIGKILL);
while nix::sys::wait::wait().unwrap().pid().is_some() {}
});
loop {}
}
ForkResult::Child => {
execv(
CString::new("/bin/sh").unwrap().as_ref(),
&[
CString::new("/bin/sh").unwrap(),
CString::new("-c").unwrap(),
CString::new(command).unwrap(),
],
)
.unwrap();
unreachable!(
"Child fork called exec, thus the process was replaced by the command the user provided"
);
}
}
}
}
}
pub(super) fn kill(&self) { pub(super) fn kill(&self) -> Result<()> {
SCRIPT_VAR_CHILDREN.lock().unwrap().retain(|item| *item != self.child.id()); SCRIPT_VAR_CHILDREN.lock().unwrap().retain(|item| *item != self.pid as u32);
terminate_pid(self.child.id()); terminate_pid(self.pid as u32).context("Error manually killing tail-var script")
} }
} }
impl Drop for ScriptVarProcess { impl Drop for ScriptVarProcess {
fn drop(&mut self) { fn drop(&mut self) {
self.kill(); let _ = self.kill();
} }
} }
} }