Статьи

Self-Pipe Trick

Представьте, что ваш серверный процесс должен одновременно ожидать ввода-вывода для нескольких дескрипторов, а также должен ожидать доставки сигнала.
Проблема в том, что вы не можете безопасно смешивать select () с сигналами, потому что есть вероятность состояния гонки.

Рассмотрим следующий фрагмент кода:

GOT_SIGNAL = False
 
def handler(signum, frame):
    global GOT_SIGNAL
    GOT_SIGNAL = True
 
...
 
signal.signal(signal.SIGUSR1, handler)
 
# What if the signal arrives at this point?
 
try:
    reads, writes, excs = select.select(rlist, wlist, elist)
except select.error as e:
    code, msg = e.args
    if code == errno.EINTR:
        if GOT_SIGNAL:
            print 'Got signal'
    else:
        raise

Проблема здесь в том, что если SIGUSR1 доставляется после установки обработчика сигнала, но перед вызовом select, то вызов select блокируется, и мы не будем выполнять логику нашего приложения в ответ на событие, таким образом, фактически «пропуская» сигнал (наше приложение Логика в этом случае печатает сообщение: « Получил сигнал» ).

Это пример возможных неприятных гонок. Давайте смоделируем это с selsigrace.py

Запустить программу

$ python selsigrace.py
PID: 32324
Sleep for 10 secs

и отправьте сигнал USR1 на PID (он отличается при каждом запуске) в течение 10-секундного интервала, пока процесс все еще спит:

$ kill -USR1 32324

 Вы должны увидеть, как программа выводит дополнительную строку вывода «Проснись и блокируй в« select »» и блокируй без выхода , нет сообщения «Got signal»:

$ python selsigrace.py
PID: 32324
Sleep for 10 secs
Wake up and block in "select"

 Если вы отправите еще один сигнал USR1 в этот момент, то выбор будет прерван, и программа завершит работу с сообщением:

$ kill -USR1 32324
$ python selsigrace.py
PID: 32324
Sleep for 10 secs
Wake up and block in "select"
Got signal

Self-Pipe Trick используется, чтобы избежать условий гонки при ожидании сигналов и вызове select для набора дескрипторов.

Следующие шаги описывают, как реализовать это:

  1. Создайте канал и измените его чтение и запись, чтобы они были неблокирующими
  2. Добавьте конец канала для чтения в список чтения дескрипторов, данных для выбора.
  3. Установите обработчик сигнала для сигнала, который нас интересует. Когда сигнал поступает, обработчик сигнала записывает байт данных в канал. Поскольку конец записи канала неблокирует, мы предотвращаем ситуацию, когда сигналы затопляют процесс, канал заполняется, и процесс блокирует себя в обработчике сигналов.
  4. Когда выбор успешно возвращается, проверьте, находится ли конец чтения канала в списке для чтения, и если это так, то наш сигнал поступил.
  5. Когда поступает сигнал, прочитайте все байты, которые находятся в канале, и выполните любые действия, которые необходимо выполнить в ответ на доставку сигнала.

Вы можете проверить реализацию на GitHub и попробовать.

Запустите selfpipe.py и отправьте ему сигнал USR1 . Вы должны увидеть вывод сообщения « Получил сигнал» и выход.