为什么Python的Event.wait()在某些系统上会被信号打断,而在其他系统上却不会被信号打断?

时间:2018-06-28 13:17:10

标签: python signals

考虑以下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(特别是Docker的python:3.6.5-stretch)上运行它并快速按Ctrl + C时,它立即被中断:

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

但是,当我在Alpine(特别是Docker的python:3.6.5-alpine3.7)上运行它并快速按Ctrl + C组合键时,整个等待过程就结束了:

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

造成这种差异的原因是什么?其中一种系统不正确吗?

1 个答案:

答案 0 :(得分:0)

简短版本:

Python假设sem_timedwait将在信号等待时以EINTR返回。 Glibc(Debian的libc)可以做到这一点,但是POSIX表示这样做是可选的,而musl(Alpine的libc)则不这样做。

长版:

Python的Event在内部是built around Condition,它本身是built around 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's documentation

  

锁定获取现在可以被POSIX上的信号打断。

假设这部分文档是正确的,则意味着Debian上的行为是正确的,而Alpine上的行为是不正确的。

Python的acquirebuilt around sem_timedwait(假设它存在,在Debian和Alpine上都存在。如果不存在,则应该是built around pthread_cond_timedwait)。

以下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秒钟后退出,并显示“信号中断了Got”。在Alpine上,它会在2秒后退出并显示“超时”。

sem_timedwait是一个libc函数defined by POSIX。特别是,它指出EINTR“可能”失败,而不是“应该”。这意味着glibc(Debian的)和musl(高山的)都不正确。

对于historical reasons due to bugs in old kernels,是made the conscious decision to not support EINTR where they don't have to

我认为,这里的错误在于Python依赖POSIX的可选功能。事实证明,在使用pthread_cond_timedwait的情况下,由于不存在信号量,Python曾被a similar issue咬过。同样,此问题也会导致one of Python's self-tests在针对musl构建时失败。我为此打开了Python bug #34004