问题描述
让我们有一个小小的程序,该程序应该捕获(并忽略)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.py
作为独立的python脚本(python3 nosigterm.py
)执行并且由系统kill
命令(在另一个终端中)发送的SIGTERM,它的行为应为: / p>
$ 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),所有这些都具有相同的结果。如果我用用C编写的捕获SIGTERM的二进制应用程序(通过nosigterm.py
代替sigaction()
,则该行为仍然没有改变,因此它一定程度上与父python进程有关。
还要注意,Popen参数restore_signals=True/False
或preexec_fn=os.setsid/os.setpgrp
也没有做任何更改。
如果有人能帮助我理解这一点,我将不胜感激。谢谢。
解决方法
这是比赛条件。
您正在分叉并立即发送信号,因此这是子进程忽略它之前的一个竞赛。
此外,您的父脚本在检查脚本是否已死亡时具有竞争条件。您向脚本发出信号,并立即检查脚本是否已死,因此这是孩子在检查之前死亡的竞赛。
如果在发送信号之前添加time.sleep(1)
,则可以确保孩子赢得比赛,并因此获得预期的行为。