Перейти к содержимому

signal – Установка обработчиков асинхронных событий

Исходный код: Lib/signal.py


Этот модуль предоставляет механизмы для использования обработчиков сигналов в Python.

General rulesОбщие правила

Функция signal.signal() позволяет определять пользовательские обработчики, которые будут выполняться при получении сигнала. Установлено небольшое количество обработчиков по умолчанию: SIGPIPE игнорируется (поэтому ошибки записи в каналы и сокеты могут сообщаться как обычные исключения Python), а SIGINT преобразуется в исключение KeyboardInterrupt, если родительский процесс не изменил его.

Обработчик для конкретного сигнала после установки остается активным до тех пор, пока не будет явно сброшен (Python эмулирует интерфейс в стиле BSD независимо от базовой реализации), за исключением обработчика для SIGCHLD, который следует базовой реализации.

На платформах WebAssembly сигналы эмулируются и поэтому ведут себя иначе. Некоторые функции и сигналы на этих платформах недоступны.

Execution of Python signal handlersВыполнение обработчиков сигналов Python

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

  • Мало смысла в перехвате синхронных ошибок, таких как SIGFPE или SIGSEGV, вызванных некорректной операцией в C-коде. Python вернется из обработчика сигнала в C-код, который, скорее всего, снова вызовет тот же сигнал, что приведет к видимому зависанию Python. Начиная с Python 3.3, можно использовать модуль faulthandler для сообщения о синхронных ошибках.

  • Длительное вычисление, реализованное чисто на C (например, сопоставление регулярного выражения с большим объемом текста), может выполняться без прерывания в течение произвольного времени, независимо от полученных сигналов. Обработчики сигналов Python будут вызваны после завершения вычисления.

  • Если обработчик вызывает исключение, оно будет возбуждено «из ниоткуда» в главном потоке. См. примечание ниже для обсуждения.

Signals and threadsСигналы и потоки

Обработчики сигналов Python всегда выполняются в главном потоке Python главного интерпретатора, даже если сигнал был получен в другом потоке. Это означает, что сигналы нельзя использовать как средство межпотокового взаимодействия. Вместо этого можно использовать примитивы синхронизации из модуля threading.

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

Предупреждение

Примитивы синхронизации, такие как threading.Lock, не следует использовать внутри обработчиков сигналов. Это может привести к непредвиденным взаимоблокировкам.

Module contentsСодержимое модуля

Изменено в версии 3.5: константы signal (SIG*), handler (SIG_DFL, SIG_IGN) и sigmask (SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK), перечисленные ниже, были превращены в enums (соответственно Signals, Handlers и Sigmasks). Функции getsignal(), pthread_sigmask(), sigpending() и sigwait() теперь возвращают человекочитаемые enums в виде объектов Signals.

Модуль signal определяет три перечисления:

class signal.Signals

enum.IntEnum коллекция констант SIG* и констант CTRL_*.

Добавлено в версии 3.5.

class signal.Handlers

enum.IntEnum коллекция констант SIG_DFL и SIG_IGN.

Добавлено в версии 3.5.

class signal.Sigmasks

enum.IntEnum коллекция констант SIG_BLOCK, SIG_UNBLOCK и SIG_SETMASK.

Доступность: Unix.

Смотрите man-страницу sigprocmask(2) и pthread_sigmask(3) для получения дополнительной информации.

Добавлено в версии 3.5.

В модуле signal определены следующие переменные:

signal.SIG_DFL

Это один из двух стандартных вариантов обработки сигналов; он просто выполняет функцию по умолчанию для данного сигнала. Например, в большинстве систем действие по умолчанию для SIGQUIT – сбросить дамп ядра и завершиться, а для SIGCHLD – просто игнорировать его.

signal.SIG_IGN

Это ещё один стандартный обработчик сигналов, который просто игнорирует данный сигнал.

signal.SIGABRT

Сигнал прерывания от abort(3).

signal.SIGALRM

Сигнал таймера от alarm(2).

signal.SIGBREAK

Прерывание с клавиатуры (CTRL + BREAK).

signal.SIGBUS

Ошибка шины (неверный доступ к памяти).

signal.SIGCHLD

Дочерний процесс остановлен или завершён.

signal.SIGCLD

Псевдоним для SIGCHLD.

Доступность: не macOS.

signal.SIGCONT

Продолжить выполнение процесса, если он в данный момент остановлен.

signal.SIGFPE

Исключение с плавающей запятой. Например, деление на ноль.

Смотрите также

ZeroDivisionError возникает, если второй аргумент операции деления или взятия остатка равен нулю.

signal.SIGHUP

Обнаружен разрыв связи на управляющем терминале или завершение управляющего процесса.

signal.SIGILL

Недопустимая инструкция.

signal.SIGINT

Прерывание с клавиатуры (CTRL + C).

Действие по умолчанию – возбудить KeyboardInterrupt.

signal.SIGKILL

Сигнал уничтожения.

Его нельзя перехватить, заблокировать или игнорировать.

signal.SIGPIPE

Разорванный канал: запись в канал при отсутствии читателей.

Действие по умолчанию – игнорировать сигнал.

signal.SIGPROF

Истекло время профилирования.

signal.SIGQUIT

Сигнал выхода из терминала.

signal.SIGSEGV

Ошибка сегментации: недопустимая ссылка на память.

signal.SIGSTOP

Остановка выполнения (нельзя перехватить или игнорировать).

signal.SIGSTKFLT

Ошибка стека на сопроцессоре. Ядро Linux не генерирует этот сигнал: он может быть сгенерирован только в пользовательском пространстве.

Доступность: Linux.

На архитектурах, где этот сигнал доступен. См. справочную страницу signal(7) для получения дополнительной информации.

Добавлено в версии 3.11.

signal.SIGTERM

Сигнал завершения.

signal.SIGUSR1

Пользовательский сигнал 1.

signal.SIGUSR2

Пользовательский сигнал 2.

signal.SIGVTALRM

Виртуальный таймер истёк.

signal.SIGWINCH

Сигнал изменения размера окна.

signal.SIGXCPU

Превышен лимит процессорного времени.

SIG*

Все номера сигналов определены символически. Например, сигнал разъединения определяется как signal.SIGHUP; имена переменных идентичны именам, используемым в программах на C, как в <signal.h>. Справочная страница Unix для ‘signal’ перечисляет существующие сигналы (в некоторых системах это signal(2), в других список находится в signal(7)). Обратите внимание, что не все системы определяют одинаковый набор имён сигналов; только те имена, которые определены системой, определяются этим модулем.

signal.CTRL_C_EVENT

Сигнал, соответствующий нажатию клавиш Ctrl+C. Этот сигнал можно использовать только с os.kill().

Добавлено в версии 3.2.

signal.CTRL_BREAK_EVENT

Сигнал, соответствующий событию нажатия клавиш Ctrl+Break. Этот сигнал может использоваться только с os.kill().

Добавлено в версии 3.2.

signal.NSIG

На единицу больше, чем номер самого старшего сигнала. Используйте valid_signals() для получения допустимых номеров сигналов.

signal.ITIMER_REAL

Уменьшает таймер интервалов в реальном времени и отправляет SIGALRM по истечении.

signal.ITIMER_VIRTUAL

Уменьшает таймер интервалов только при выполнении процесса и отправляет SIGVTALRM по истечении.

signal.ITIMER_PROF

Уменьшает таймер интервалов как при выполнении процесса, так и при выполнении системы от имени процесса. В паре с ITIMER_VIRTUAL этот таймер обычно используется для профилирования времени, затрачиваемого приложением в пользовательском и системном пространстве. SIGPROF отправляется по истечении.

signal.SIG_BLOCK

Возможное значение параметра how функции pthread_sigmask(), указывающее, что сигналы должны быть заблокированы.

Добавлено в версии 3.3.

signal.SIG_UNBLOCK

Возможное значение параметра how функции pthread_sigmask(), указывающее, что сигналы должны быть разблокированы.

Добавлено в версии 3.3.

signal.SIG_SETMASK

Возможное значение параметра how функции pthread_sigmask(), указывающее, что маска сигналов должна быть заменена.

Добавлено в версии 3.3.

Модуль signal определяет одно исключение:

exception signal.ItimerError

Возбуждается для сообщения об ошибке в реализации setitimer() или getitimer(). Ожидайте этой ошибки, если в setitimer() передан недопустимый интервальный таймер или отрицательное время. Эта ошибка является подтипом OSError.

Добавлено в версии 3.3: Раньше эта ошибка была подтипом IOError, который теперь является псевдонимом OSError.

Модуль signal определяет следующие функции:

signal.alarm(time)

Если time не равно нулю, эта функция запрашивает отправку сигнала SIGALRM процессу через time секунд. Любой ранее запланированный будильник отменяется (в любой момент может быть запланирован только один будильник). Возвращаемое значение – количество секунд до того, как должен был сработать ранее установленный будильник. Если time равно нулю, будильник не планируется, а любой запланированный будильник отменяется. Если возвращаемое значение равно нулю, будильник в данный момент не запланирован.

Доступность: Unix.

Дополнительную информацию см. в man-странице alarm(2).

signal.getsignal(signalnum)

Возвращает текущий обработчик сигнала для сигнала signalnum. Возвращаемое значение может быть вызываемым объектом Python или одним из специальных значений signal.SIG_IGN, signal.SIG_DFL или None. Здесь signal.SIG_IGN означает, что сигнал ранее игнорировался, signal.SIG_DFL – что ранее использовался способ обработки сигнала по умолчанию, а None – что предыдущий обработчик сигнала не был установлен из Python.

signal.strsignal(signalnum)

Возвращает описание сигнала signalnum, например «Interrupt» для SIGINT. Возвращает None, если signalnum не имеет описания. Вызывает ValueError, если signalnum недопустим.

Добавлено в версии 3.8.

signal.valid_signals()

Возвращает набор допустимых номеров сигналов на этой платформе. Он может быть меньше range(1, NSIG), если некоторые сигналы зарезервированы системой для внутреннего использования.

Добавлено в версии 3.8.

signal.pause()

Приводит к приостановке процесса до получения сигнала; затем будет вызван соответствующий обработчик. Ничего не возвращает.

Доступность: Unix.

Дополнительную информацию см. в man-странице signal(2).

См. также sigwait(), sigwaitinfo(), sigtimedwait() и sigpending().

signal.raise_signal(signum)

Отправляет сигнал вызывающему процессу. Ничего не возвращает.

Добавлено в версии 3.8.

signal.pidfd_send_signal(pidfd, sig, siginfo=None, flags=0)

Отправляет сигнал sig процессу, на который указывает файловый дескриптор pidfd. В настоящее время Python не поддерживает параметр siginfo; он должен быть равен None. Аргумент flags предусмотрен для будущих расширений; в настоящее время флаги не определены.

См. справочную страницу pidfd_send_signal(2) для получения дополнительной информации.

Доступность: Linux >= 5.1, Android >= build-time API level 31

Добавлено в версии 3.9.

signal.pthread_kill(thread_id, signalnum)

Отправляет сигнал signalnum потоку thread_id, другому потоку в том же процессе, что и вызывающий. Целевой поток может выполнять любой код (как Python, так и нет). Однако, если целевой поток выполняет интерпретатор Python, обработчики сигналов Python будут выполнены главным потоком главного интерпретатора. Следовательно, единственный смысл отправки сигнала конкретному потоку Python – заставить выполняющийся системный вызов завершиться с ошибкой InterruptedError.

Используйте threading.get_ident() или атрибут ident объектов threading.Thread, чтобы получить подходящее значение для thread_id.

Если signalnum равен 0, то сигнал не отправляется, но проверка ошибок всё равно выполняется; это можно использовать для проверки, работает ли целевой поток.

Вызывает аудируемое событие signal.pthread_kill с аргументами thread_id, signalnum.

Доступность: Unix.

См. справочную страницу pthread_kill(3) для дополнительной информации.

См. также os.kill().

Добавлено в версии 3.3.

signal.pthread_sigmask(how, mask)

Получает и/или изменяет маску сигналов вызывающего потока. Маска сигналов – это набор сигналов, доставка которых в настоящее время заблокирована для вызывающего. Возвращает старую маску сигналов в виде набора сигналов.

Поведение вызова зависит от значения how, как описано ниже.

  • SIG_BLOCK: Набор заблокированных сигналов является объединением текущего набора и аргумента mask.

  • SIG_UNBLOCK: Сигналы из mask удаляются из текущего набора заблокированных сигналов. Допускается попытка разблокировать сигнал, который не заблокирован.

  • SIG_SETMASK: Набор заблокированных сигналов устанавливается равным аргументу mask.

mask – это набор номеров сигналов (например, {signal.SIGINT, signal.SIGTERM}). Используйте valid_signals() для полной маски, включающей все сигналы.

Например, signal.pthread_sigmask(signal.SIG_BLOCK, []) читает маску сигналов вызывающего потока.

SIGKILL и SIGSTOP не могут быть заблокированы.

Доступность: Unix.

См. справочные страницы sigprocmask(2) и pthread_sigmask(3) для дополнительной информации.

См. также pause(), sigpending() и sigwait().

Добавлено в версии 3.3.

signal.setitimer(which, seconds, interval=0)

Устанавливает заданный интервальный таймер (один из signal.ITIMER_REAL, signal.ITIMER_VIRTUAL или signal.ITIMER_PROF), указанный параметром which, чтобы он срабатывал через seconds (округляется до микросекунд, в отличие от alarm()) и затем каждые interval секунд (если interval не равен нулю). Интервальный таймер, указанный which, можно сбросить, установив seconds в ноль.

Когда интервальный таймер срабатывает, процессу отправляется сигнал. Отправляемый сигнал зависит от используемого таймера; signal.ITIMER_REAL доставит SIGALRM, signal.ITIMER_VIRTUAL отправляет SIGVTALRM, а signal.ITIMER_PROF доставит SIGPROF.

Старые значения возвращаются в виде кортежа из двух чисел с плавающей запятой: (delay, interval).

Попытка передать недопустимый интервальный таймер вызовет ItimerError.

Изменено в версии 3.15: Принимает любые вещественные числа в качестве seconds и interval, а не только целые или числа с плавающей запятой.

signal.getitimer(which)

Возвращает текущее значение заданного интервального таймера, указанного параметром which.

signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)

Устанавливает файловый дескриптор пробуждения в fd. Когда получен сигнал, для которого программа зарегистрировала обработчик, номер сигнала записывается в виде одного байта в fd. Если вы не зарегистрировали обработчик для интересующих вас сигналов, то в fd пробуждения ничего не будет записано. Это может использоваться библиотекой для пробуждения вызова poll или select, позволяя полностью обработать сигнал.

Возвращается старый fd пробуждения (или -1, если пробуждение через файловый дескриптор не было включено). Если fd равен -1, пробуждение через файловый дескриптор отключено. Если не -1, fd должен быть неблокирующим. Библиотеке необходимо удалить все байты из fd перед повторным вызовом poll или select.

Если потоки включены, эту функцию можно вызывать только из главного потока главного интерпретатора; попытка вызвать её из других потоков приведёт к возникновению исключения ValueError.

Есть два распространённых способа использования этой функции. В обоих подходах fd используется для пробуждения при поступлении сигнала, но они различаются тем, как определяют, какой сигнал или какие сигналы поступили.

В первом подходе мы читаем данные из буфера fd, и значения байтов дают номера сигналов. Это просто, но в редких случаях может возникнуть проблема: обычно fd имеет ограниченный объём буфера, и если слишком много сигналов поступает слишком быстро, буфер может переполниться, и некоторые сигналы могут быть потеряны. Если вы используете этот подход, вам следует установить warn_on_full_buffer=True, что, по крайней мере, приведёт к выводу предупреждения в stderr при потере сигналов.

Во втором подходе мы используем fd пробуждения только для пробуждения и игнорируем реальные значения байтов. В этом случае нас интересует только то, пуст ли буфер fd или нет; полный буфер вовсе не указывает на проблему. Если вы используете этот подход, вам следует установить warn_on_full_buffer=False, чтобы ваши пользователи не сбивались с толку ложными предупреждающими сообщениями.

Изменено в версии 3.5: В Windows функция теперь также поддерживает сокетные дескрипторы.

Изменено в версии 3.7: Добавлен параметр warn_on_full_buffer.

signal.siginterrupt(signalnum, flag)

Изменяет поведение перезапуска системных вызовов: если flag равен False, системные вызовы будут перезапущены при прерывании сигналом signalnum, в противном случае системные вызовы будут прерваны. Ничего не возвращает.

Доступность: Unix.

Смотрите man-страницу siginterrupt(3) для получения дополнительной информации.

Обратите внимание, что установка обработчика сигнала с помощью signal() сбросит поведение перезапуска на прерываемое, неявно вызывая siginterrupt() со значением flag, равным истине, для данного сигнала.

signal.signal(signalnum, handler)

Устанавливает обработчик для сигнала signalnum равным функции handler. handler может быть вызываемым объектом Python, принимающим два аргумента (см. ниже), или одним из специальных значений signal.SIG_IGN или signal.SIG_DFL. Возвращается предыдущий обработчик сигнала (см. описание getsignal() выше). (Смотрите man-страницу Unix signal(2) для дополнительной информации.)

Если потоки включены, эту функцию можно вызывать только из главного потока главного интерпретатора; попытка вызвать её из других потоков приведёт к возникновению исключения ValueError.

handler вызывается с двумя аргументами: номером сигнала и текущим стековым фреймом (None или объектом фрейма; описание объектов фрейма смотрите в описании в иерархии типов или в описаниях атрибутов в модуле inspect).

В Windows signal() можно вызывать только с SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM или SIGBREAK. В любом другом случае будет возбуждено ValueError. Обратите внимание, что не все системы определяют одинаковый набор имён сигналов; будет возбуждено AttributeError, если имя сигнала не определено как константа уровня модуля SIG*.

signal.sigpending()

Проверяет набор сигналов, ожидающих доставки вызывающему потоку (т.е. сигналы, которые были возбуждены, пока были заблокированы). Возвращает набор ожидающих сигналов.

Доступность: Unix.

Смотрите man-страницу sigpending(2) для получения дополнительной информации.

Смотрите также pause(), pthread_sigmask() и sigwait().

Добавлено в версии 3.3.

signal.sigwait(sigset)

Приостанавливает выполнение вызывающего потока до доставки одного из сигналов, указанных в наборе сигналов sigset. Функция принимает сигнал (удаляет его из списка ожидающих сигналов) и возвращает номер сигнала.

Доступность: Unix.

Смотрите man-страницу sigwait(3) для получения дополнительной информации.

Смотрите также pause(), pthread_sigmask(), sigpending(), sigwaitinfo() и sigtimedwait().

Добавлено в версии 3.3.

signal.sigwaitinfo(sigset)

Приостанавливает выполнение вызывающего потока до доставки одного из сигналов, указанных в наборе сигналов sigset. Функция принимает сигнал и удаляет его из списка ожидающих сигналов. Если один из сигналов в sigset уже ожидает доставки для вызывающего потока, функция немедленно возвращается с информацией об этом сигнале. Обработчик сигнала не вызывается для доставленного сигнала. Функция возбуждает InterruptedError, если она прервана сигналом, который не входит в sigset.

Возвращаемое значение – это объект, представляющий данные, содержащиеся в структуре siginfo_t, а именно: si_signo, si_code, si_errno, si_pid, si_uid, si_status, si_band.

Доступность: Unix.

Смотрите man-страницу sigwaitinfo(2) для получения дополнительной информации.

Смотрите также pause(), sigwait() и sigtimedwait().

Добавлено в версии 3.3.

Изменено в версии 3.5: Теперь функция повторяется, если она прервана сигналом, не входящим в sigset, и обработчик сигнала не возбуждает исключение (см. PEP 475 для обоснования).

signal.sigtimedwait(sigset, timeout)

Как и sigwaitinfo(), но принимает дополнительный аргумент timeout, задающий тайм-аут. Если timeout задан как 0, выполняется опрос. Возвращает None при наступлении тайм-аута.

Доступность: Unix.

Дополнительную информацию см. на странице руководства sigtimedwait(2).

См. также pause(), sigwait() и sigwaitinfo().

Добавлено в версии 3.3.

Изменено в версии 3.5: Теперь функция повторяется с пересчитанным timeout, если прервана сигналом, не входящим в sigset, и обработчик сигнала не вызывает исключение (обоснование см. в PEP 475).

Изменено в версии 3.15: Принимает любое вещественное число в качестве timeout, а не только целое или число с плавающей запятой.

ExamplesПримеры

Вот минимальный пример программы. В ней используется функция alarm() для ограничения времени ожидания открытия файла; это полезно, если файл относится к последовательному устройству, которое может быть не включено, что обычно приводит к бесконечной блокировке os.open(). Решение – установить 5-секундный будильник перед открытием файла; если операция занимает слишком много времени, будет отправлен сигнал будильника, и обработчик вызовет исключение.

python
import signal, os

def handler(signum, frame):
    signame = signal.Signals(signum).name
    print(f'Signal handler called with signal {signame} ({signum})')
    raise OSError("Couldn't open device!")

# Установить обработчик сигнала и будильник на 5 секунд
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)

# Этот вызов open() может зависнуть на неопределенный срок
fd = os.open('/dev/ttyS0', os.O_RDWR)

signal.alarm(0)          # Отключить будильник

Note on SIGPIPEПримечание о SIGPIPE

Перенаправление вывода вашей программы в такие инструменты, как head(1), приведёт к отправке сигнала SIGPIPE вашему процессу, когда получатель его стандартного вывода закроется раньше времени. В результате возникнет исключение вроде BrokenPipeError: [Errno 32] Broken pipe. Чтобы обработать эту ситуацию, оберните точку входа для перехвата этого исключения следующим образом:

python
import os
import sys

def main():
    try:
        # симуляция большого вывода (ваш код заменяет этот цикл)
        for x in range(10000):
            print("y")
        # сбросить вывод здесь, чтобы принудительно вызвать SIGPIPE
        # пока внутри этого блока try.
        sys.stdout.flush()
    except BrokenPipeError:
        # Python сбрасывает стандартные потоки при выходе; перенаправляет оставшийся вывод
        # в devnull, чтобы избежать повторного BrokenPipeError при завершении
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
        sys.exit(1)  # Python завершается с кодом ошибки 1 при EPIPE

if __name__ == '__main__':
    main()

Не устанавливайте обработку SIGPIPE в SIG_DFL во избежание BrokenPipeError. Это приведёт к неожиданному завершению программы при любом прерывании сокетного соединения, пока программа ещё записывает в него.

Note on Signal Handlers and ExceptionsПримечание об обработчиках сигналов и исключениях

Если обработчик сигнала вызывает исключение, это исключение распространяется на главный поток и может возникнуть после любой инструкции байткода. В частности, KeyboardInterrupt может появиться в любой точке выполнения. Большинство кода Python, включая стандартную библиотеку, нельзя сделать устойчивым к этому, поэтому KeyboardInterrupt (или любое другое исключение, возникшее из обработчика сигнала) может в редких случаях привести программу в непредвиденное состояние.

Чтобы проиллюстрировать эту проблему, рассмотрим следующий код:

python
class SpamContext:
    def __init__(self):
        self.lock = threading.Lock()

    def __enter__(self):
        # Если здесь возникнет KeyboardInterrupt, всё в порядке
        self.lock.acquire()
        # Если здесь возникнет KeyboardInterrupt, __exit__ не будет вызван
        ...
        # KeyboardInterrupt может возникнуть прямо перед возвратом функции

    def __exit__(self, exc_type, exc_val, exc_tb):
        ...
        self.lock.release()

Для многих программ, особенно тех, которые просто хотят завершиться по KeyboardInterrupt, это не проблема, но приложения, которые являются сложными или требуют высокой надёжности, должны избегать вызова исключений из обработчиков сигналов. Им также следует избегать перехвата KeyboardInterrupt как способа корректного завершения. Вместо этого они должны установить собственный обработчик SIGINT. Ниже приведён пример HTTP-сервера, который избегает KeyboardInterrupt:

python
import signal
import socket
from selectors import DefaultSelector, EVENT_READ
from http.server import HTTPServer, SimpleHTTPRequestHandler

interrupt_read, interrupt_write = socket.socketpair()

def handler(signum, frame):
    print('Signal handler called with signal', signum)
    interrupt_write.send(b'\0')
signal.signal(signal.SIGINT, handler)

def serve_forever(httpd):
    sel = DefaultSelector()
    sel.register(interrupt_read, EVENT_READ)
    sel.register(httpd, EVENT_READ)

    while True:
        for key, _ in sel.select():
            if key.fileobj == interrupt_read:
                interrupt_read.recv(1)
                return
            if key.fileobj == httpd:
                httpd.handle_request()

print("Serving on port 8000")
httpd = HTTPServer(('', 8000), SimpleHTTPRequestHandler)
serve_forever(httpd)
print("Shutdown...")