fix(os): attempt to stop children with SIGTERM before SIGKILL (#601)
* fix(os): attempt to stop children with SIGTERM before SIGKILL * style(fmt): make rustfmt happy
This commit is contained in:
parent
9c419a0a4f
commit
47206866b6
4 changed files with 46 additions and 22 deletions
|
|
@ -54,8 +54,9 @@ pub(crate) fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
|
||||||
/// Handle some signals for the child process. This will loop until the child
|
/// Handle some signals for the child process. This will loop until the child
|
||||||
/// process exits.
|
/// process exits.
|
||||||
fn handle_command_exit(mut child: Child) {
|
fn handle_command_exit(mut child: Child) {
|
||||||
// register the SIGINT signal (TODO handle more signals)
|
let mut should_exit = false;
|
||||||
let mut signals = signal_hook::iterator::Signals::new(&[SIGINT]).unwrap();
|
let mut attempts = 3;
|
||||||
|
let mut signals = signal_hook::iterator::Signals::new(&[SIGINT, SIGTERM]).unwrap();
|
||||||
'handle_exit: loop {
|
'handle_exit: loop {
|
||||||
// test whether the child process has exited
|
// test whether the child process has exited
|
||||||
match child.try_wait() {
|
match child.try_wait() {
|
||||||
|
|
@ -66,17 +67,26 @@ fn handle_command_exit(mut child: Child) {
|
||||||
break 'handle_exit;
|
break 'handle_exit;
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
::std::thread::sleep(::std::time::Duration::from_millis(100));
|
::std::thread::sleep(::std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
Err(e) => panic!("error attempting to wait: {}", e),
|
Err(e) => panic!("error attempting to wait: {}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
for signal in signals.pending() {
|
if !should_exit {
|
||||||
if let SIGINT = signal {
|
for signal in signals.pending() {
|
||||||
child.kill().unwrap();
|
if signal == SIGINT || signal == SIGTERM {
|
||||||
child.wait().unwrap();
|
should_exit = true;
|
||||||
break 'handle_exit;
|
}
|
||||||
}
|
}
|
||||||
|
} else if attempts > 0 {
|
||||||
|
// let's try nicely first...
|
||||||
|
attempts -= 1;
|
||||||
|
kill(Pid::from_raw(child.id() as i32), Some(Signal::SIGTERM)).unwrap();
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// when I say whoa, I mean WHOA!
|
||||||
|
let _ = child.kill();
|
||||||
|
break 'handle_exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,10 +100,7 @@ fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, P
|
||||||
Ok(fork_pty_res) => {
|
Ok(fork_pty_res) => {
|
||||||
let pid_primary = fork_pty_res.master;
|
let pid_primary = fork_pty_res.master;
|
||||||
let pid_secondary = match fork_pty_res.fork_result {
|
let pid_secondary = match fork_pty_res.fork_result {
|
||||||
ForkResult::Parent { child } => {
|
ForkResult::Parent { child } => child,
|
||||||
// fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
|
|
||||||
child
|
|
||||||
}
|
|
||||||
ForkResult::Child => {
|
ForkResult::Child => {
|
||||||
let child = Command::new(cmd.command)
|
let child = Command::new(cmd.command)
|
||||||
.args(&cmd.args)
|
.args(&cmd.args)
|
||||||
|
|
@ -205,8 +212,10 @@ pub trait ServerOsApi: Send + Sync {
|
||||||
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error>;
|
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error>;
|
||||||
/// Wait until all output written to the object referred to by `fd` has been transmitted.
|
/// Wait until all output written to the object referred to by `fd` has been transmitted.
|
||||||
fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error>;
|
fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error>;
|
||||||
/// Terminate the process with process ID `pid`.
|
/// Terminate the process with process ID `pid`. (SIGTERM)
|
||||||
fn kill(&self, pid: Pid) -> Result<(), nix::Error>;
|
fn kill(&self, pid: Pid) -> Result<(), nix::Error>;
|
||||||
|
/// Terminate the process with process ID `pid`. (SIGKILL)
|
||||||
|
fn force_kill(&self, pid: Pid) -> Result<(), nix::Error>;
|
||||||
/// Returns a [`Box`] pointer to this [`ServerOsApi`] struct.
|
/// Returns a [`Box`] pointer to this [`ServerOsApi`] struct.
|
||||||
fn box_clone(&self) -> Box<dyn ServerOsApi>;
|
fn box_clone(&self) -> Box<dyn ServerOsApi>;
|
||||||
/// Receives a message on server-side IPC channel
|
/// Receives a message on server-side IPC channel
|
||||||
|
|
@ -252,16 +261,14 @@ impl ServerOsApi for ServerOsInputOutput {
|
||||||
Box::new((*self).clone())
|
Box::new((*self).clone())
|
||||||
}
|
}
|
||||||
fn kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
fn kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
||||||
// TODO:
|
kill(pid, Some(Signal::SIGTERM)).unwrap();
|
||||||
// Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which
|
|
||||||
// the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck
|
|
||||||
// that's why we're sending SIGKILL here
|
|
||||||
// A better solution would be to send SIGINT here and not wait for it, and then have
|
|
||||||
// a background thread do the waitpid stuff and send SIGKILL if the process is stuck
|
|
||||||
kill(pid, Some(Signal::SIGKILL)).unwrap();
|
|
||||||
waitpid(pid, None).unwrap();
|
waitpid(pid, None).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn force_kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
||||||
|
let _ = kill(pid, Some(Signal::SIGKILL));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) {
|
fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) {
|
||||||
self.receive_instructions_from_client
|
self.receive_instructions_from_client
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
||||||
|
|
@ -273,9 +273,20 @@ impl Pty {
|
||||||
PaneId::Terminal(id) => {
|
PaneId::Terminal(id) => {
|
||||||
let child_pid = self.id_to_child_pid.remove(&id).unwrap();
|
let child_pid = self.id_to_child_pid.remove(&id).unwrap();
|
||||||
let handle = self.task_handles.remove(&id).unwrap();
|
let handle = self.task_handles.remove(&id).unwrap();
|
||||||
self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap();
|
|
||||||
task::block_on(async {
|
task::block_on(async {
|
||||||
handle.cancel().await;
|
self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap();
|
||||||
|
let timeout = Duration::from_millis(100);
|
||||||
|
match async_timeout(timeout, handle.cancel()).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
_ => {
|
||||||
|
self.bus
|
||||||
|
.os_input
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.force_kill(child_pid)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
PaneId::Plugin(pid) => drop(
|
PaneId::Plugin(pid) => drop(
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
||||||
Box::new((*self).clone())
|
Box::new((*self).clone())
|
||||||
}
|
}
|
||||||
|
fn force_kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
fn force_kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue