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:
parent
606625a3aa
commit
29644bd44c
@ -6,6 +6,7 @@
|
|||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
import logging
|
import logging
|
||||||
|
import signal
|
||||||
import asyncio
|
import asyncio
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ class SCProcess(asyncio.subprocess.Process):
|
|||||||
self.log_stderr = log_stderr
|
self.log_stderr = log_stderr
|
||||||
self.program = program
|
self.program = program
|
||||||
self.partial_data = b""
|
self.partial_data = b""
|
||||||
|
self.cancel_requested = False
|
||||||
|
|
||||||
async def _read_stream_with_cb(self, fd):
|
async def _read_stream_with_cb(self, fd):
|
||||||
transport = self._transport.get_pipe_transport(fd)
|
transport = self._transport.get_pipe_transport(fd)
|
||||||
@ -36,10 +38,28 @@ class SCProcess(asyncio.subprocess.Process):
|
|||||||
transport.close()
|
transport.close()
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def cancel(self):
|
async def cancel(self):
|
||||||
self.stdout.feed_eof()
|
if self.cancel_requested:
|
||||||
self.stderr.feed_eof()
|
return
|
||||||
self.terminate()
|
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):
|
async def communicate_with_cb(self, input=None):
|
||||||
if input is not None:
|
if input is not None:
|
||||||
@ -70,10 +90,10 @@ class ShellCommand:
|
|||||||
self.cancelled = False
|
self.cancelled = False
|
||||||
self.return_code = None
|
self.return_code = None
|
||||||
|
|
||||||
def cancel(self):
|
async def cancel(self):
|
||||||
self.cancelled = True
|
self.cancelled = True
|
||||||
if self.proc is not None:
|
if self.proc is not None:
|
||||||
self.proc.cancel()
|
await self.proc.cancel()
|
||||||
|
|
||||||
def get_return_code(self):
|
def get_return_code(self):
|
||||||
return self.return_code
|
return self.return_code
|
||||||
@ -101,7 +121,7 @@ class ShellCommand:
|
|||||||
await asyncio.wait_for(ret, timeout=timeout)
|
await asyncio.wait_for(ret, timeout=timeout)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
complete = False
|
complete = False
|
||||||
self.proc.terminate()
|
await self.proc.cancel()
|
||||||
else:
|
else:
|
||||||
complete = not self.cancelled
|
complete = not self.cancelled
|
||||||
return self._check_proc_success(complete)
|
return self._check_proc_success(complete)
|
||||||
@ -118,7 +138,7 @@ class ShellCommand:
|
|||||||
ret, timeout=timeout)
|
ret, timeout=timeout)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
complete = False
|
complete = False
|
||||||
self.proc.terminate()
|
await self.proc.cancel()
|
||||||
else:
|
else:
|
||||||
complete = not self.cancelled
|
complete = not self.cancelled
|
||||||
if self.log_stderr and stderr:
|
if self.log_stderr and stderr:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user