# `signal` — Set handlers for asynchronous events

Source code: Lib/signal.py

Цей модуль надає механізми для використання обробників сигналів у Python.

## Загальні правила

Функція `signal.signal()` дозволяє визначати спеціальні обробники, які будуть виконуватися під час отримання сигналу. Встановлено невелику кількість обробників за замовчуванням: `SIGPIPE` ігнорується (тому про помилки запису в каналах і сокетах можна повідомляти як про звичайні винятки Python), а `SIGINT` перетворюється на `KeyboardInterrupt` виняток, якщо батьківський процес не змінив його.

Обробник для певного сигналу після встановлення залишається встановленим, доки його явно не буде скинуто (Python емулює інтерфейс у стилі BSD незалежно від базової реалізації), за винятком обробника для `SIGCHLD`, який слідує за базовою реалізацією. .

On WebAssembly platforms `wasm32-emscripten` and `wasm32-wasi`, signals are emulated and therefore behave differently. Several functions and signals are not available on these platforms.

### Виконання обробників сигналів Python

Обробник сигналу Python не виконується всередині обробника сигналу низького рівня (C). Натомість обробник сигналу низького рівня встановлює прапорець, який повідомляє virtual machine виконати відповідний обробник сигналу Python пізніше (наприклад, у наступній інструкції bytecode). Це має наслідки:

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

- Тривале обчислення, реалізоване виключно на C (наприклад, зіставлення регулярного виразу з великою частиною тексту), може виконуватися безперервно протягом довільного проміжку часу, незалежно від будь-яких отриманих сигналів. Обробники сигналів Python будуть викликані, коли обчислення завершиться.

- Якщо обробник викликає виняткову ситуацію, вона буде викликана «з повітря» в основному потоці. Перегляньте примітку нижче для обговорення.

### Сигнали та нитки

Обробники сигналів Python завжди виконуються в основному потоці Python головного інтерпретатора, навіть якщо сигнал було отримано в іншому потоці. Це означає, що сигнали не можна використовувати як засіб міжпотокового зв’язку. Натомість ви можете використовувати примітиви синхронізації з модуля `threading`.

Крім того, лише головному потоку головного інтерпретатора дозволено встановлювати новий обробник сигналу.

## Зміст модуля

Змінено в версії 3.5: signal (SIG*), handler (`SIG_DFL`, `SIG_IGN`) and sigmask (`SIG_BLOCK`, `SIG_UNBLOCK`, `SIG_SETMASK`) related constants listed below were turned into `enums` (`Signals`, `Handlers` and `Sigmasks` respectively). `getsignal()`, `pthread_sigmask()`, `sigpending()` and `sigwait()` functions return human-readable `enums` as `Signals` objects.

The signal module defines three enums:

class signal.Signals

  `enum.IntEnum` collection of SIG* constants and the CTRL_* constants.

  Нове в версії 3.5.

class signal.Handlers

  `enum.IntEnum` collection the constants `SIG_DFL` and `SIG_IGN`.

  Нове в версії 3.5.

class signal.Sigmasks

  `enum.IntEnum` collection the constants `SIG_BLOCK`, `SIG_UNBLOCK` and `SIG_SETMASK`.

  Availability: Unix.

  See the man page sigprocmask(2) and pthread_sigmask(3) for further information.

  Нове в версії 3.5.

Змінні, визначені в модулі `signal`:

signal.SIG_DFL

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

signal.SIG_IGN

  Це ще один стандартний обробник сигналу, який просто ігнорує поданий сигнал.

signal.SIGABRT

  Сигнал скасування з abort(3).

signal.SIGALRM

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

  Availability: Unix.

signal.SIGBREAK

  Переривання з клавіатури (CTRL + BREAK).

  Availability: Windows.

signal.SIGBUS

  Помилка шини (поганий доступ до пам’яті).

  Availability: Unix.

signal.SIGCHLD

  Дочірній процес зупинено або припинено.

  Availability: Unix.

signal.SIGCLD

  Псевдонім `SIGCHLD`.

  Availability: not macOS.

signal.SIGCONT

  Продовжуйте процес, якщо він зараз зупинений

  Availability: Unix.

signal.SIGFPE

  Виняток із плаваючою комою. Наприклад, ділення на нуль.

  Дивись також

  `ZeroDivisionError` виникає, коли другий аргумент операції ділення або модуля дорівнює нулю.

signal.SIGHUP

  Виявлено зависання на керуючому терміналі або смерть керуючого процесу.

  Availability: Unix.

signal.SIGILL

  Незаконна інструкція.

signal.SIGINT

  Переривання з клавіатури (CTRL + C).

  Дія за замовчуванням — підняти `KeyboardInterrupt`.

signal.SIGKILL

  Сигнал вбивства.

  Його неможливо зловити, заблокувати чи проігнорувати.

  Availability: Unix.

signal.SIGPIPE

  Зламаний канал: записувати в канал без читачів.

  Стандартна дія – ігнорувати сигнал.

  Availability: Unix.

signal.SIGSEGV

  Помилка сегментації: недійсне посилання на пам’ять.

signal.SIGSTKFLT

  > Stack fault on coprocessor. The Linux kernel does not raise this signal: it can only be raised in user space.

  Availability: Linux.

  On architectures where the signal is available. See the man page signal(7) for further information.

  Нове в версії 3.11.

signal.SIGTERM

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

signal.SIGUSR1

  Визначений користувачем сигнал 1.

  Availability: Unix.

signal.SIGUSR2

  Визначений користувачем сигнал 2.

  Availability: Unix.

signal.SIGWINCH

  Сигнал зміни розміру вікна.

  Availability: Unix.

SIG*

  Усі сигнальні числа визначені символічно. Наприклад, сигнал зависання визначається як `signal.SIGHUP`; імена змінних ідентичні іменам, які використовуються в програмах на C, як це знайдено в `<signal.h>`. Сторінка довідки Unix для „`signal()`“ містить список існуючих сигналів (у деяких системах це signal(2), в інших список знаходиться в signal(7) ). Зауважте, що не всі системи визначають однаковий набір назв сигналів; тільки ті імена, які визначені системою, визначаються цим модулем.

signal.CTRL_C_EVENT

  Сигнал, що відповідає події натискання клавіші ``Ctrl`+`C``. Цей сигнал можна використовувати лише з `os.kill()`.

  Availability: Windows.

  Нове в версії 3.2.

signal.CTRL_BREAK_EVENT

  Сигнал, що відповідає події натискання клавіші ``Ctrl`+`Break``. Цей сигнал можна використовувати лише з `os.kill()`.

  Availability: Windows.

  Нове в версії 3.2.

signal.NSIG

  One more than the number of the highest signal number. Use `valid_signals()` to get valid signal numbers.

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 дорівнює нулю, будильник не заплановано, а будь-який запланований будильник скасовується. Якщо повернене значення дорівнює нулю, нагадування наразі не заплановано.

  Availability: Unix.

  See the man page alarm(2) for further information.

signal.getsignal(signalnum)

  Повертає поточний обробник сигналу для сигналу signalnum. Поверненим значенням може бути об’єкт Python, який можна викликати, або одне зі спеціальних значень `signal.SIG_IGN`, `signal.SIG_DFL` або `None`. Тут `signal.SIG_IGN` означає, що сигнал раніше ігнорувався, `signal.SIG_DFL` означає, що стандартний спосіб обробки сигналу використовувався раніше, а `None` означає, що попередній обробник сигналів не було встановлено з Python.

signal.strsignal(signalnum)

  Returns the description of signal signalnum, such as «Interrupt» for `SIGINT`. Returns `None` if signalnum has no description. Raises `ValueError` if signalnum is invalid.

  Нове в версії 3.8.

signal.valid_signals()

  Повернути набір дійсних номерів сигналу на цій платформі. Це може бути менше ніж діапазон (1, NSIG) якщо деякі сигнали зарезервовані системою для внутрішнього використання.

  Нове в версії 3.8.

signal.pause()

  Перевести процес у режим сну, доки не буде отримано сигнал; потім буде викликано відповідний обробник. Нічого не повертає.

  Availability: Unix.

  See the man page signal(2) for further information.

  Дивіться також `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) для отримання додаткової інформації.

  Availability: Linux >= 5.1

  Нове в версії 3.9.

signal.pthread_kill(thread_id, signalnum)

  Надішліть сигнал signalnum до потоку thread_id, іншого потоку в тому самому процесі, що й абонент. Цільовий потік може виконувати будь-який код (Python чи ні). Однак, якщо цільовий потік виконує інтерпретатор Python, обробники сигналів Python будуть виконуватися головним потоком основного інтерпретатора. Таким чином, єдиним пунктом надсилання сигналу до певного потоку Python було б примусово завершити запущений системний виклик з `InterruptedError`.

  Use `threading.get_ident()` or the `ident` attribute of `threading.Thread` objects to get a suitable value for thread_id.

  Якщо signalnum дорівнює 0, тоді сигнал не надсилається, але перевірка помилок все одно виконується; це можна використовувати, щоб перевірити, чи цільовий потік все ще працює.

  Викликає подію аудиту `signal.pthread_kill` з аргументами `thread_id`, `signalnum`.

  Availability: Unix.

  See the man page pthread_kill(3) for further information.

  Дивіться також `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` не можна заблокувати.

  Availability: Unix.

  See the man page sigprocmask(2) and pthread_sigmask(3) for further information.

  Дивіться також `pause()`, `sigpending()` і `sigwait()`.

  Нове в версії 3.3.

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

  Встановлює заданий інтервальний таймер (один із `signal.ITIMER_REAL`, `signal.ITIMER_VIRTUAL` або `signal.ITIMER_PROF`), визначений який запускати через секунд (дозволено число, відмінний від `alarm()`) і після цього кожні інтервал секунд (якщо інтервал відмінний від нуля). Інтервальний таймер, визначений яким, можна скинути, встановивши секунд на нуль.

  Коли спрацьовує інтервальний таймер, процесу надсилається сигнал. Надісланий сигнал залежить від таймера, який використовується; `signal.ITIMER_REAL` доставить `SIGALRM`, `signal.ITIMER_VIRTUAL` надішле `SIGVTALRM`, а `signal.ITIMER_PROF` доставить `SIGPROF` .

  Старі значення повертаються як кортеж: (затримка, інтервал).

  Спроба передати недійсний таймер інтервалу спричинить `ItimerError`.

  Availability: Unix.

signal.getitimer(which)

  Повертає поточне значення заданого інтервального таймера, визначеного which.

  Availability: Unix.

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

  Set the wakeup file descriptor to fd. When a signal is received, the signal number is written as a single byte into the fd. This can be used by a library to wakeup a poll or select call, allowing the signal to be fully processed.

  Повертається старий fd пробудження (або -1, якщо пробудження дескриптора файлу не було ввімкнено). Якщо fd дорівнює -1, пробудження дескриптора файлу вимкнено. Якщо не -1, fd має бути неблокуючим. Бібліотека має видалити будь-які байти з fd перед повторним викликом опитування або вибору.

  Коли потоки ввімкнено, цю функцію можна викликати лише з основного потоку головного інтерпретатора; спроба викликати його з інших потоків призведе до виникнення винятку `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, інакше системні виклики буде перервано. Нічого не повертає.

  Availability: Unix.

  See the man page siginterrupt(3) for further information.

  Note that installing a signal handler with `signal()` will reset the restart behaviour to interruptible by implicitly calling `siginterrupt()` with a true flag value for the given signal.

signal.signal(signalnum, handler)

  Встановіть обробник для сигналу signalnum на функцію handler. обробник може бути викликаним об’єктом Python, який приймає два аргументи (див. нижче) або одне зі спеціальних значень `signal.SIG_IGN` або `signal.SIG_DFL`. Буде повернено попередній обробник сигналу (див. опис `getsignal()` вище). (Див. сторінку довідки Unix signal(2) для отримання додаткової інформації.)

  Коли потоки ввімкнено, цю функцію можна викликати лише з основного потоку головного інтерпретатора; спроба викликати його з інших потоків призведе до виникнення винятку `ValueError`.

  Обробник викликається з двома аргументами: номером сигналу та поточним фреймом стека (`None` або об’єкт фрейму; для опису об’єктів фрейму див. опис в ієрархії типів або див. описи атрибутів у модулі `inspect`).

  У Windows `signal()` можна викликати лише за допомогою `SIGABRT`, `SIGFPE`, `SIGILL`, `SIGINT`, `SIGSEGV`, `SIGTERM` або `SIGBREAK`. Помилка `ValueError` буде викликана в будь-якому іншому випадку. Зауважте, що не всі системи визначають однаковий набір назв сигналів; `AttributeError` буде викликано, якщо назва сигналу не визначена як константа рівня модуля `SIG*`.

signal.sigpending()

  Перевірте набір сигналів, які очікують на доставку до викликаючого потоку (тобто сигнали, які були підняті під час блокування). Повертає набір незавершених сигналів.

  Availability: Unix.

  See the man page sigpending(2) for further information.

  Дивіться також `pause()`, `pthread_sigmask()` і `sigwait()`.

  Нове в версії 3.3.

signal.sigwait(sigset)

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

  Availability: Unix.

  See the man page sigwait(3) for further information.

  Дивіться також `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`.

  Availability: Unix.

  See the man page sigwaitinfo(2) for further information.

  Дивіться також `pause()`, `sigwait()` і `sigtimedwait()`.

  Нове в версії 3.3.

  Змінено в версії 3.5: Тепер функція виконується повторно, якщо її перериває сигнал не в sigset, і обробник сигналу не викликає винятку (див. PEP 475 для обґрунтування).

signal.sigtimedwait(sigset, timeout)

  Like `sigwaitinfo()`, but takes an additional timeout argument specifying a timeout. If timeout is specified as `0`, a poll is performed. Returns `None` if a timeout occurs.

  Availability: Unix.

  See the man page sigtimedwait(2) for further information.

  Дивіться також `pause()`, `sigwait()` і `sigwaitinfo()`.

  Нове в версії 3.3.

  Змінено в версії 3.5: Тепер функція виконується повторно з повторно обчисленим тайм-аутом, якщо її перериває сигнал не в sigset, і обробник сигналу не викликає виняткової ситуації (див. PEP 475 для обґрунтування).

## Приклади

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

    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!")

    # Set the signal handler and a 5-second alarm
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(5)

    # This open() may hang indefinitely
    fd = os.open('/dev/ttyS0', os.O_RDWR)

    signal.alarm(0)          # Disable the alarm

## Примітка щодо SIGPIPE

Передача виводу вашої програми до таких інструментів, як head(1), призведе до надсилання сигналу `SIGPIPE` до вашого процесу, коли отримувач його стандартного виводу закривається раніше. Це призводить до виключення на зразок `BrokenPipeError: [Errno 32] Broken pipe`. Щоб впоратися з цим випадком, оберніть свою точку входу, щоб перехопити цю виняток, наступним чином:

    import os
    import sys

    def main():
        try:
            # simulate large output (your code replaces this loop)
            for x in range(10000):
                print("y")
            # flush output here to force SIGPIPE to be triggered
            # while inside this try block.
            sys.stdout.flush()
        except BrokenPipeError:
            # Python flushes standard streams on exit; redirect remaining output
            # to devnull to avoid another BrokenPipeError at shutdown
            devnull = os.open(os.devnull, os.O_WRONLY)
            os.dup2(devnull, sys.stdout.fileno())
            sys.exit(1)  # Python exits with error code 1 on EPIPE

    if __name__ == '__main__':
        main()

Не встановлюйте розташування `SIGPIPE` як `SIG_DFL`, щоб уникнути `BrokenPipeError`. Це спричинило б несподіваний вихід вашої програми щоразу, коли будь-яке з’єднання з сокетом переривається, поки ваша програма все ще записує в нього.

## Примітка щодо обробників сигналів і винятків

Якщо обробник сигналу викликає виняток, виняток буде передано в основний потік і може бути викликано після будь-якої інструкції bytecode. Зокрема, `KeyboardInterrupt` може з’явитися в будь-який момент під час виконання. Більшість коду Python, включаючи стандартну бібліотеку, не можна зробити стійкими до цього, і тому `KeyboardInterrupt` (або будь-який інший виняток, що є результатом обробки сигналу) може в рідкісних випадках переводити програму в неочікуваний стан.

Щоб проілюструвати цю проблему, розглянемо такий код:

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

        def __enter__(self):
            # If KeyboardInterrupt occurs here, everything is fine
            self.lock.acquire()
            # If KeyboardInterrupt occurs here, __exit__ will not be called
            ...
            # KeyboardInterrupt could occur just before the function returns

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

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

    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...")