在 subprocess.TimeoutExpired 终止子进程

Terminate child process on subprocess.TimeoutExpired

我有以下代码片段:

    def terminal_command(command, timeout=5*60):
        """Executes a terminal command."""
        cmd = command.split(" ")

        timer = time.strftime('%Hh %Mm %Ss', time.gmtime(timeout))

        proc = None

        try:
            proc = subprocess.run(cmd, timeout=timeout, capture_output=True)
        except subprocess.TimeoutExpired:
            print("Timeout")
            proc.terminate()
            reason = "timeout"
            stdout = b'error'
            stderr = b'error'

        if proc != None:
            # Finished!

            stdout = proc.stdout
            stderr = proc.stderr
            reason = "finished"

        return stdout.decode('utf-8').strip(), stderr.decode('utf-8').strip(), reason

我 运行 一个命令,它花费的时间明显超过 5 分钟。在这种情况下,subprocess.run 引发异常,但 proc 现在是 None,所以我不能使用 proc.terminate()。当代码终止时,正如其他地方所记录的那样,子进程继续 运行。我想终止它。

有什么方法可以在 TimeoutExpired 上终止子进程,同时重定向输出?我在 Linux 系统上,所以我愿意接受 Popen 的要求,但理想情况下我希望它是跨平台的。

四个月后:我明白了。

核心问题似乎是将 os.killsignal.SIGKILL 一起使用并不能正确终止进程。

将我的代码修改为以下作品。

    def custom_terminal_command(self, command, timeout=5*60, cwd=None):
        with subprocess.Popen(command.split(" "), preexec_fn=os.setsid) as process:
            wd = os.getcwd()
            try:
                if cwd is not None:
                    # Man fuck linux
                    for d in cwd.split("/"):
                        os.chdir(d)
                stdout, stderr = process.communicate(None, timeout=timeout)
            except subprocess.TimeoutExpired as exc:
                import signal

                os.killpg(os.getpgid(process.pid), signal.SIGTERM)

                try:
                    import msvcrt
                except ModuleNotFoundError:
                    _mswindows = False
                else:
                    _mswindows = True

                if _mswindows:
                    # Windows accumulates the output in a single blocking
                    # read() call run on child threads, with the timeout
                    # being done in a join() on those threads.  communicate()
                    # _after_ kill() is required to collect that and add it
                    # to the exception.
                    exc.stdout, exc.stderr = process.communicate()
                else:
                    # POSIX _communicate already populated the output so
                    # far into the TimeoutExpired exception.
                    process.wait()
                reason = 'timeout'
                stdout, stderr = process.communicate()
            except:  # Including KeyboardInterrupt, communicate handled that.
                process.kill()
                # We don't call process.wait() as .__exit__ does that for us.
                reason = 'other'
                stdout, stderr = process.communicate()
                raise
            else:
                reason = 'finished'
            finally:
                os.chdir(wd)

            try:
                return stdout.decode('utf-8').strip(), stderr.decode('utf-8').strip(), reason
            except AttributeError:
                try:
                    return stdout.strip(), stderr.strip(), reason
                except AttributeError:
                    return stdout, stderr, reason

请参阅以下 SO post 进行简短讨论:How to terminate a python subprocess launched with shell=True