【问题标题】:Python subprocess kill with timeoutPython子进程超时终止
【发布时间】:2018-02-13 09:25:34
【问题描述】:

我正在 python 中使用 subprocess 模块运行一些 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:

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

【问题讨论】:

  • 您是否尝试过使用 Popen 的旧方法?
  • 您的except 块中有什么?来自文档:“如果超时到期,子进程不会被杀死,因此为了正确清理,行为良好的应用程序应该杀死子进程并完成通信
  • 我看过官方文档,它发送SIGKILL杀死子进程。也许你的脚本不能被SIGKILL杀死?在原始终端中尝试一下。
  • @cdarke 这是Popen 的行为,但不是runrun 将杀死子进程。
  • @Sraw:对不起,你是对的。不过,我仍然想知道except 块中的内容。

标签: python subprocess


【解决方案1】:

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

来自docs for subprocess.run

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

但在您的情况下,您使用的是shell=True,我以前也看到过类似的问题,因为阻塞进程是 shell 进程的子进程。

如果您正确分解参数并且您的脚本具有正确的 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,但它是同一件事):

import subprocess,time

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

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

import subprocess,time

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

=> 记事本在 1 秒后关闭

有趣的是,如果您删除 time.sleep()kill() 甚至可以与 shell=True 一起工作,可能是因为它成功杀死了正在启动 notepad 的 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()

(来源:how to kill process and child processes from python?

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

【讨论】:

  • run的源代码中,也使用kill()来终止子进程。所以在这种情况下,我认为这行不通。但谁知道...
  • @Sraw:我认为是血腥的 shell=True 导致了这个问题。
  • 我......仍然不确定,因为我从未见过这种行为。既然你说你看到了,能不能帮我们提供一个会导致这种情况的例子?
  • @Sraw 其实我可以。我用的是windows,但我相信它也可以在Linux上重现。
  • 由于某些原因,我需要在shell中执行命令,所以shell=true是必须的。当我不使用该语句时,我得到一个FileNotFoundError。还是这个错误是其他错误配置造成的?
猜你喜欢
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 2011-01-21
  • 1970-01-01
  • 2014-10-25
  • 2018-03-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多