在KeyboardInterrupt时终止子进程链
我在写一个脚本来启动本地的 JBoss 实例时遇到了一个奇怪的问题。
我的代码大概是这样的:
with open("/var/run/jboss/jboss.pid", "wb") as f:
process = subprocess.Popen(["/opt/jboss/bin/standalone.sh", "-b=0.0.0.0"])
f.write(str(process.pid))
try:
process.wait()
except KeyboardInterrupt:
process.kill()
这个代码应该挺简单的,就是在程序运行的时候把进程 ID 写到一个文件里,当我按下 KeyboardInterrupt
(通常是 Ctrl+C)的时候,就结束这个子进程。
问题是,当我发送结束信号的时候,JBoss 仍然在后台运行,似乎这个信号没有传递到由 standalone.sh
启动的 Java 进程。
我喜欢用 Python 来写系统管理脚本,但像这样的奇怪情况很多,如果我用 Bash 来写,所有东西就会顺利运行™。
我该怎么做才能在收到 KeyboardInterrupt
的时候结束整个子进程树呢?
1 个回答
3
你可以使用psutil
这个库来实现这个功能:
import psutil
#..
proc = psutil.Process(process.pid)
for child in proc.children(recursive=True):
child.kill()
proc.kill()
据我所知,subprocess
模块并没有提供获取子进程的API函数,os
模块也是如此。
杀掉进程的一个更好的方法可能是这样的:
proc = psutil.Process(process.pid)
procs = proc.children(recursive=True)
procs.append(proc)
for proc in procs:
proc.terminate()
gone, alive = psutil.wait_procs(procs, timeout=1)
for p in alive:
p.kill()
这样做可以给进程一个正确结束的机会,当超时时间到时,剩下的进程会被强制结束。
需要注意的是,psutil
还提供了一个Popen
类,它的接口和subprocess.Popen
是一样的,但多了psutil.Process
的额外功能。你可能会想直接使用这个,而不是subprocess.Popen
。这个方法也更安全,因为psutil
会检查进程ID(PID)在进程结束后不会被重复使用,而subprocess
则没有这个检查。