shell_command: Attempt to cleanly exit cancelled commands

Rather than use terminate to pre-emptively exit a shell command, attempt to exit with SIGINT, SIGTERM, and finally SIGKILL.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-03-06 14:06:28 -05:00
parent 606625a3aa
commit 29644bd44c
1 changed files with 28 additions and 8 deletions

View File

@ -6,6 +6,7 @@
import os
import shlex
import logging
import signal
import asyncio
from tornado import gen
@ -15,6 +16,7 @@ class SCProcess(asyncio.subprocess.Process):
self.log_stderr = log_stderr
self.program = program
self.partial_data = b""
self.cancel_requested = False
async def _read_stream_with_cb(self, fd):
transport = self._transport.get_pipe_transport(fd)
@ -36,10 +38,28 @@ class SCProcess(asyncio.subprocess.Process):
transport.close()
return output
def cancel(self):
self.stdout.feed_eof()
self.stderr.feed_eof()
self.terminate()
async def cancel(self):
if self.cancel_requested:
return
self.cancel_requested = True
exit_success = False
for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGKILL]:
self.send_signal(sig)
try:
ret = self.wait()
await asyncio.wait_for(ret, timeout=2.)
except asyncio.TimeoutError:
continue
logging.debug(f"Command '{self.program}' exited with "
f"signal: {sig.name}")
exit_success = True
break
if not exit_success:
logging.info(f"WARNING: {self.program} did not cleanly exit")
if self.stdout is not None:
self.stdout.feed_eof()
if self.stderr is not None:
self.stderr.feed_eof()
async def communicate_with_cb(self, input=None):
if input is not None:
@ -70,10 +90,10 @@ class ShellCommand:
self.cancelled = False
self.return_code = None
def cancel(self):
async def cancel(self):
self.cancelled = True
if self.proc is not None:
self.proc.cancel()
await self.proc.cancel()
def get_return_code(self):
return self.return_code
@ -101,7 +121,7 @@ class ShellCommand:
await asyncio.wait_for(ret, timeout=timeout)
except asyncio.TimeoutError:
complete = False
self.proc.terminate()
await self.proc.cancel()
else:
complete = not self.cancelled
return self._check_proc_success(complete)
@ -118,7 +138,7 @@ class ShellCommand:
ret, timeout=timeout)
except asyncio.TimeoutError:
complete = False
self.proc.terminate()
await self.proc.cancel()
else:
complete = not self.cancelled
if self.log_stderr and stderr: