Python 子进程终止并超时 [英] Python subprocess kill with timeout

查看:45
本文介绍了Python 子进程终止并超时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 python 中使用 subprocess 模块运行一些 shell 脚本.如果 shell 脚本运行时间过长,我喜欢终止子进程.我认为如果我将 timeout=30 传递给我的 run(..) 语句就足够了.

I am running some shell scripts with the subprocess module in python. If the shell scripts is running to long, I like to kill the subprocess. I thought it will be enough if I am passing the timeout=30 to my run(..) statement.

代码如下:

try:
    result=run(['utilities/shell_scripts/{0} {1} {2}'.format(
                        self.language_conf[key][1], self.proc_dir, config.main_file)],
                shell=True,
                check=True,
                stdout=PIPE,
                stderr=PIPE, 
                universal_newlines=True, 
                timeout=30,
                bufsize=100)
except TimeoutExpired as timeout:

我已经用一些运行 120 秒的 shell 脚本测试了这个调用.我预计子进程会在 30 秒后被终止,但实际上该进程正在完成 120 秒的脚本,然后引发超时异常.现在的问题是如何通过超时终止子进程?

I have tested this call with some shell scripts that runs 120s. I expected the subprocess to be killed after 30s, but in fact the process is finishing the 120s script and than raises the Timeout Exception. Now the Question how can I kill the subprocess by timeout?

推荐答案

文档明确指出应该终止进程:

The documentation explicitly states that the process should be killed:

来自文档,用于subprocess.run:

超时参数传递给 Popen.communicate().如果超时到期,子进程将被杀死并等待.在子进程终止后,将重新引发 TimeoutExpired 异常."

"The timeout argument is passed to Popen.communicate(). If the timeout expires, the child process will be killed and waited for. The TimeoutExpired exception will be re-raised after the child process has terminated."

但在您的情况下,您使用的是 shell=True,而且我之前也遇到过类似的问题,因为阻塞进程是 shell 进程的子进程.

But in your case you're using shell=True, and I've seen issues like that before, because the blocking process is a child of the shell process.

如果你正确分解你的参数并且你的脚本有正确的shebang,我认为你不需要shell=True.你可以试试这个:

I don't think you need shell=True if you decompose your arguments properly and your scripts have the proper shebang. You could try this:

result=run(
  [os.path.join('utilities/shell_scripts',self.language_conf[key][1]), self.proc_dir, config.main_file],  # don't compose argument line yourself
            shell=False,  # no shell wrapper
            check=True,
            stdout=PIPE,
            stderr=PIPE, 
            universal_newlines=True, 
            timeout=30,
            bufsize=100)

注意,我可以很容易地在 Windows 上重现这个问题(使用 Popen,但它是一样的):

note that I can reproduce this issue very easily on Windows (using Popen, but it's the same thing):

import subprocess,time

p=subprocess.Popen("notepad",shell=True)
time.sleep(1)
p.kill()

=> 记事本保持打开状态,可能是因为它设法与父 shell 进程分离.

=> notepad stays open, probably because it manages to detach from the parent shell process.

import subprocess,time

p=subprocess.Popen("notepad",shell=False)
time.sleep(1)
p.kill()

=> 记事本在 1 秒后关闭

=> notepad closes after 1 second

有趣的是,如果您删除 time.sleep()kill() 甚至可以与 shell=True 一起使用,可能是因为它成功杀死了正在启动 notepad 的 shell.

Funnily enough, if you remove time.sleep(), kill() works even with shell=True probably because it successfully kills the shell which is launching notepad.

我并不是说您有完全相同的问题,我只是证明 shell=True 出于多种原因是邪恶的,并且无法杀死/超时进程是另一个原因原因.

I'm not saying you have exactly the same issue, I'm just demonstrating that shell=True is evil for many reasons, and not being able to kill/timeout the process is one more reason.

但是,如果出于某种原因需要 shell=True,您可以使用 psutil 最终杀死所有孩子.在这种情况下,最好使用 Popen 以便直接获取进程 ID:

However, if you need shell=True for a reason, you can use psutil to kill all the children in the end. In that case, it's better to use Popen so you get the process id directly:

import subprocess,time,psutil

parent=subprocess.Popen("notepad",shell=True)
for _ in range(30): # 30 seconds
    if parent.poll() is not None:  # process just ended
      break
    time.sleep(1)
else:
   # the for loop ended without break: timeout
   parent = psutil.Process(parent.pid)
   for child in parent.children(recursive=True):  # or parent.children() for recursive=False
       child.kill()
   parent.kill()

(来源:如何从蟒蛇?)

那个例子也杀死了记事本实例.

that example kills the notepad instance as well.

这篇关于Python 子进程终止并超时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆