Почему использование threading.Event приводит к тому, что SIGTERM не попадает?

У меня есть поточный демон Python. Как любой хороший демон, он хочет запустить все свои рабочие потоки, а затем подождать, пока не будет объявлено о завершении. Нормальный сигнал для завершения - SIGTERM, и на большинстве языков я бы остановился, ожидая события или мьютекса, поэтому использование threading.Event имело смысл для меня. Проблема в том, что объекты Python Event и сигналы Unix не отображаются хорошо вместе.

Это работает как ожидалось, заканчивая на SIGTERM:

import signal
import time

RUN = True

def handle(a, b):
    global RUN
    print "handled"
    RUN = False

signal.signal(signal.SIGTERM, handle)
while RUN:
    time.sleep(0.250)
print "Stopping"

но это приводит к доставке SIGTERM (т.е. не говоря уже о прекращении работы, "обработанный" никогда не печатается):

import signal
import threading

RUN_EVENT = threading.Event()

def handle(a, b):
    print "handled"
    RUN_EVENT.set()

signal.signal(signal.SIGTERM, handle)
RUN_EVENT.wait()
print "Stopping"

Итак, мой вопрос:

  • Я неправильно использую threading.Event?
  • Если это не так, есть ли альтернатива, отличная от механизма опроса и сна, из первого примера?
  • Также, если это не так, почему использование threading.Event убивает обработчик сигнала?

Ответы

Ответ 1

От Документация Python по сигналам:

Хотя обработчики сигналов Python называются асинхронно, что касается пользователя Python, они могут встречаться только между "атомарными" инструкциями интерпретатора Python. Это означает, что сигналы, поступающие во время длительных вычислений, выполненных исключительно на C (например, совпадения регулярных выражений на больших телах текста), могут задерживаться на произвольное время.

Я тестировал различные классы threading и thread, и никто из них не работает так, как вы этого хотите, - это, вероятно, из-за того, как Python обрабатывает сигналы.

В signal, однако, существует функция pause(), которая спит до тех пор, пока процесс не получит сигнал. Ваш модифицированный пример будет выглядеть так:

import signal

RUN = True

def handle(a, b):
    global RUN
    print "handled"
    RUN = False

signal.signal(signal.SIGTERM, handle)
while RUN:
    signal.pause()
print "Stopping"

Я проверил его на Linux, он работает. Я не думаю, что он больше классифицируется как опрос и сон, если ваше приложение не использует много других сигналов.

Ответ 2

В Python 3 это работает:

from threading import Event
from os import getpid
from signal import SIGTERM, SIGINT, signal

stop_event = Event()

def handler(signum, frame):
    stop_event.set()

def main():
    signal(SIGTERM, handler)
    signal(SIGINT, handler)
    print('start, pid:', getpid())
    stop_event.wait()
    print('done')

if __name__ == '__main__':
    main()

Похоже, что в Python 2.7 это работает, только если вы указали интервал ожидания: stop_event.wait(number_of_seconds)