Почему Python Event.wait () может прерываться сигналами в некоторых системах, но не в других? - PullRequest
0 голосов
/ 28 июня 2018

Рассмотрим следующий скрипт Python:

from datetime import datetime
from threading import Event

event = Event()
start = datetime.now()

try:
    event.wait(5)
except KeyboardInterrupt:
    print("caught Ctrl+C after %s" % (datetime.now() - start))

Когда я запускаю его на Debian (в частности, на Python Docker: 3.6.5-stretch) и быстро нажимаю Ctrl + C, он немедленно прерывается:

# python mptest.py
^Ccaught Ctrl+C after 0:00:00.684854
# 

Но когда я запускаю его на Alpine (в частности, на Python Docker's: 3.6.5-alpine3.7) и быстро нажимаю Ctrl + C, весь процесс ожидания завершается:

/ # python mptest.py 
^Ccaught Ctrl+C after 0:00:05.000314
/ # 

В чем причина этой разницы? Одна из систем неверна?

1 Ответ

0 голосов
/ 30 июня 2018

Короткая версия:

Python предполагает, что sem_timedwait вернется с EINTR, если сигнал прервет его во время ожидания. Glibc (libc Debian) делает это, но POSIX говорит, что делать это необязательно, а musl (libc Alpine) этого не делает.

Длинная версия:

Python Event - это , построенный вокруг Condition внутри, что само по себе построено вокруг Lock. Следующая программа демонстрирует то же поведение по той же причине, что и Lock:

from datetime import datetime
from threading import Lock

lock = Lock()
lock.acquire()
start = datetime.now()

try:
    lock.acquire(True, 5)
except KeyboardInterrupt:
    print("caught Ctrl+C after %s" % (datetime.now() - start))

Из Документация Python :

Получение блокировки теперь может быть прервано сигналами в POSIX.

Предполагая, что этот фрагмент документации верен, это означает, что поведение в Debian является правильным, а поведение в Alpine - неправильным.

Python acquire - это , построенный вокруг sem_timedwait (при условии, что он присутствует, который присутствует как в Debian, так и в Alpine. Если бы его не было, он вместо этого был бы построен вокруг pthread_cond_timedwait ** 1040).

Следующая программа на C демонстрирует несоответствие sem_timedwait при сборке на каждой из систем:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <time.h>
#include <errno.h>
#include <signal.h>

void handler(int sig) {
   puts("in signal handler");
}

int main() {
   struct sigaction sa;
   sa.sa_handler = handler;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = 0;
   sigaction(SIGALRM, &sa, NULL);

   alarm(1);

   struct timespec ts;
   clock_gettime(CLOCK_REALTIME, &ts);
   ts.tv_sec += 2;

   sem_t sem;
   sem_init(&sem, 0, 0);
   sem_timedwait(&sem, &ts);

   if(errno == EINTR) {
      puts("Got interrupted by signal");
   } else if(errno == ETIMEDOUT) {
      puts("Timed out");
   }
   return 0;
}

В Debian он выходит через 1 секунду с сообщением «Прервано по сигналу». На Alpine он выходит через 2 секунды с «Timed out».

sem_timedwait - это функция libc , определенная POSIX . В частности, в нем говорится, что он «может» потерпеть неудачу с EINTR, а не «должен». Это означает, что ни glibc (Debian), ни musl (Alpine) не верны.

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

По моему мнению, вина здесь лежит на Python, который полагается на опциональную функцию POSIX. Как оказалось, Python был укушен аналогичной проблемой ранее, в случае, когда он использует pthread_cond_timedwait из-за отсутствия семафоров. Кроме того, эта проблема приводит к сбою одного из тестов Python при сборке с musl. Я открыл Python ошибка # 34004 относительно этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...