考虑以下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
/ #
造成这种差异的原因是什么?其中一种系统不正确吗?
答案 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))
锁定获取现在可以被POSIX上的信号打断。
假设这部分文档是正确的,则意味着Debian上的行为是正确的,而Alpine上的行为是不正确的。
Python的acquire
是built 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。