diff options
| author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-03-20 14:24:47 -0400 |
|---|---|---|
| committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-03-20 14:24:47 -0400 |
| commit | 023acec19eb516dcb3b57ae2b5197e1f80af97d7 (patch) | |
| tree | 7adec8f5a55d23f16669b21fae9511d498ccbedb | |
| parent | da0f95d72656b46b374d66948c68d4055fed218c (diff) | |
| download | cmd2-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.py | 26 | ||||
| -rw-r--r-- | cmd2/utils.py | 22 |
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 |
