带超时的Python子进程终止

带超时的Python子进程终止,python,subprocess,Python,Subprocess,我正在使用python中的子流程模块运行一些shell脚本。如果shell脚本运行时间过长,我希望终止子进程。我想如果我将timeout=30传递给我的run(…)语句就足够了 代码如下: try: result=run(['utilities/shell_scripts/{0} {1} {2}'.format( self.language_conf[key][1], self.proc_dir, config.main_file)],

我正在使用python中的子流程模块运行一些shell脚本。如果shell脚本运行时间过长,我希望终止子进程。我想如果我将
timeout=30
传递给我的
run(…)
语句就足够了

代码如下:

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:

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

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

从:

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

但是在您的例子中,您使用的是
shell=True
,我以前见过类似的问题,因为阻塞过程是shell过程的子过程

import subprocess,time

p=subprocess.Popen("notepad",shell=False)
time.sleep(1)
p.kill()
如果你正确地分解你的参数并且你的脚本有正确的shebang,我认为你不需要
shell=True
。你可以试试这个:

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
,但这是一样的):

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

import subprocess,time

p=subprocess.Popen("notepad",shell=False)
time.sleep(1)
p.kill()
=>记事本在1秒后关闭

有趣的是,如果删除
time.sleep()
kill()
即使与
shell=True
一起工作,可能是因为它成功地杀死了正在启动
记事本的shell

我并不是说你有完全相同的问题,我只是在证明,出于许多原因,
shell=True
是邪恶的,不能终止/超时进程是另一个原因

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

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()
(来源:)


该示例也会杀死记事本实例。

您是否使用
Popen
尝试过遗留方法?除了
块,您的
中还有什么?在文档中:“如果超时过期,子进程不会被终止,因此为了正确清理,行为良好的应用程序应该终止子进程并完成通信。”我已经阅读了官方文档,它发送
SIGKILL
来终止子进程。也许您的脚本不能被
SIGKILL
杀死?在原始终端中试用。@cdarke这是
Popen
的行为,而不是
run
run
将终止子进程。@Sraw:对不起,你说得对。我仍然想知道
块中除了
之外还有什么。在
运行
的源代码中,它还使用
kill()
终止子进程。所以在这种情况下,我认为这是行不通的。但是谁知道呢…@Sraw:我想是血壳=真的引起了这个问题。我。。。我还不确定,因为我从未见过这种行为。既然你说你已经看到了,你能帮我们一个忙,提供一个会导致这种情况的例子吗?@Sraw实际上我能。我使用的是windows,但我相信它也可以在Linux上复制。由于某些原因,我需要在shell中执行命令,因此有必要执行
shell=true
。当我不使用该语句时,我得到一个
FileNotFoundError
。或者该错误是由其他错误配置引起的?