首页 > 解决方案 > 为什么 Python 子进程无法正确捕获信号?

问题描述

让我们有一个很小的程序来捕获(并忽略)SIGTERM 信号:

# nosigterm.py:

import signal
import time

def ignore(signum, frame):
    print("Ignoring signal {}".format(signum))


if __name__ == '__main__':
  signal.signal(signal.SIGINT, ignore)
  signal.signal(signal.SIGTERM, ignore)

  while True:
    time.sleep(2)
    print("... in loop ...")

当从另一个 python 脚本作为子进程执行时,发送 SIGTERM 会终止这个子进程,我觉得这很奇怪:

# parent_script.py:

import signal
import subprocess
import sys

args = [sys.executable, "nosigterm.py"]
prog = subprocess.Popen(args)
assert prog.poll() is None

prog.send_signal(signal.SIGTERM)
print("prog.poll(): {}".format(prog.poll()))
assert prog.poll() is None, "Program unexpectedly terminated after SIGTERM"

输出是:

$ python3 parent_script.py 
prog.poll(): None
Traceback (most recent call last):
  File "parent_script.py", line 13, in <module>
    assert prog.poll() is None, "Program unexpectedly terminated after SIGTERM"
AssertionError: Program unexpectedly terminated after SIGTERM

你知道为什么会这样吗?

请注意,如果作为独立的nosigterm.pypython 脚本(python3 nosigterm.pykill

$ python3 nosigterm.py 
... in loop ...
... in loop ...
Ignoring signal 15
... in loop ...
... in loop ...
... in loop ...

我尝试了三个 python 版本(2.7、3.6 和 3.7)和两个 Linux 操作系统(CentOS 7 和 Debian 9),结果都一样。如果我用nosigterm.pyC 编写的捕获 SIGTERM (通过sigaction())的二进制应用程序替换,则行为仍然没有改变,因此它必须与父 python 进程有某种关联。

另请注意,Popen 参数restore_signals=True/Falsepreexec_fn=os.setsid/os.setpgrp没有进行任何更改。

如果有人能帮助我理解这一点,我将不胜感激。谢谢你。

标签: pythonlinuxsubprocesssignalsposix

解决方案


这是一个竞争条件。

您正在分叉并立即发送信号,因此子进程在被杀死之前忽略它是一场竞赛。

此外,您的父脚本在检查脚本是否已死亡时具有竞争条件。您向脚本发出信号并立即检查它是否已死,因此在检查发生之前,这是一场让孩子死去的竞赛。

如果您time.sleep(1)在发送信号之前添加 a,您将确保孩子赢得比赛,从而获得您期望的行为。


推荐阅读