summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2019-03-20 14:24:47 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2019-03-20 14:24:47 -0400
commit023acec19eb516dcb3b57ae2b5197e1f80af97d7 (patch)
tree7adec8f5a55d23f16669b21fae9511d498ccbedb
parentda0f95d72656b46b374d66948c68d4055fed218c (diff)
downloadcmd2-git-023acec19eb516dcb3b57ae2b5197e1f80af97d7.tar.gz
Handled issue where nested pipe processes were not being closed in the right order upon SIGINT events
-rw-r--r--cmd2/cmd2.py26
-rw-r--r--cmd2/utils.py22
2 files changed, 33 insertions, 15 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 6c0f0f90..78b48404 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1653,12 +1653,12 @@ class Cmd(cmd.Cmd):
:param signum: signal number
:param frame
"""
-
- # Save copy of pipe_proc_reader since it could theoretically change while this is running
- pipe_proc_reader = self.pipe_proc_reader
-
- if pipe_proc_reader is not None:
- pipe_proc_reader.terminate()
+ try:
+ # Forward the sigint to the current pipe process
+ self.pipe_proc_reader.send_sigint()
+ except AttributeError:
+ # Ignore since self.pipe_proc_reader was None
+ pass
# Re-raise a KeyboardInterrupt so other parts of the code can catch it
raise KeyboardInterrupt("Got a keyboard interrupt")
@@ -1908,12 +1908,24 @@ class Cmd(cmd.Cmd):
# We want Popen to raise an exception if it fails to open the process. Thus we don't set shell to True.
try:
+ # Set options to not forward signals to the pipe process. If a Ctrl-C event occurs,
+ # our sigint handler will forward it to the most recent pipe process. This makes sure
+ # pipe processes close in the right order (most recent first).
+ if sys.platform == 'win32':
+ creationflags = subprocess.CREATE_NEW_PROCESS_GROUP
+ start_new_session = False
+ else:
+ creationflags = 0
+ start_new_session = True
+
# For any stream that is a StdSim, we will use a pipe so we can capture its output
proc = \
subprocess.Popen(statement.pipe_to,
stdin=pipe_read,
stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout,
- stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr)
+ stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr,
+ creationflags=creationflags,
+ start_new_session=start_new_session)
ret_val = RedirectionSavedState(redirecting=True, self_stdout=self.stdout,
piping=True, pipe_proc_reader=self.pipe_proc_reader)
diff --git a/cmd2/utils.py b/cmd2/utils.py
index 2db6a267..29ae332a 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -345,7 +345,8 @@ class StdSim(object):
"""Clear the internal contents"""
self.buffer.byte_buf = b''
- def isatty(self) -> bool:
+ @staticmethod
+ def isatty() -> bool:
"""StdSim will never be considered an interactive stream"""
return False
@@ -403,9 +404,10 @@ class ProcReader(object):
if self._proc.stderr is not None:
self._err_thread.start()
- def terminate(self) -> None:
- """Terminates the process being run"""
- self._proc.terminate()
+ def send_sigint(self) -> None:
+ """Send a SIGINT to the process"""
+ import signal
+ self._proc.send_signal(signal.SIGINT)
def wait(self) -> None:
"""Wait for the process to finish"""
@@ -452,7 +454,11 @@ class ProcReader(object):
:param stream: the stream being written to
:param to_write: the bytes being written
"""
- if 'b' in stream.mode:
- stream.write(to_write)
- else:
- stream.buffer.write(to_write)
+ try:
+ if 'b' in stream.mode:
+ stream.write(to_write)
+ else:
+ stream.buffer.write(to_write)
+ except BrokenPipeError:
+ # This occurs if output is being piped to a process that closed
+ pass