Python 子进程终止并超时 [英] Python subprocess kill with timeout
问题描述
我在 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:
超时参数传递给 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屋!