"signal" --- 非同期イベントにハンドラーを設定する
*************************************************

**ソースコード:** Lib/signal.py

======================================================================

このモジュールでは Python でシグナルハンドラを使うための機構を提供しま
す。


一般的なルール
==============

"signal.signal()" 関数を使って、シグナルを受信した時に実行されるハンド
ラを定義することができます。 Python は標準でごく少数のシグナルハンドラ
をインストールしています: "SIGPIPE" は無視され (したがって、 pipe や
socket に対する書き込みで生じたエラーは通常の Python 例外として報告さ
れます)、 "SIGINT" は "KeyboardInterrupt" 例外に変換されます。親プロセ
スが変更していない場合は、これらはどれも上書きすることができます。

特定のシグナルに対するハンドラが一度設定されると、明示的にリセットしな
いかぎり設定されたままになります (Python は背後の実装系に関係なく BSD
形式のインターフェースをエミュレートします)。例外は "SIGCHLD" のハンド
ラで、この場合は背後の実装系の仕様に従います。

On WebAssembly platforms, signals are emulated and therefore behave
differently. Several functions and signals are not available on these
platforms.


Python のシグナルハンドラの実行
-------------------------------

A Python signal handler does not get executed inside the low-level (C)
signal handler.  Instead, the low-level signal handler sets a flag
which tells the *virtual machine* to execute the corresponding Python
signal handler at a later point (for example, at the next *bytecode*
instruction). This has consequences:

* It makes little sense to catch synchronous errors like "SIGFPE" or
  "SIGSEGV" that are caused by an invalid operation in C code.  Python
  will return from the signal handler to the C code, which is likely
  to raise the same signal again, causing Python to apparently hang.
  From Python 3.3 onwards, you can use the "faulthandler" module to
  report on synchronous errors.

* 完全にCで実装された長時間かかる計算 (大きいテキストに対する正規表現
  のマッチなど) は、どのシグナルを受信しても中断されないまま長時間実行
  され続ける可能性があります。Python のシグナルハンドラはその計算が終
  了してから呼び出されます。

* If the handler raises an exception, it will be raised "out of thin
  air" in the main thread. See the note below for a discussion.


シグナルとスレッド
------------------

Python のシグナルハンドラは、もしシグナルを受け取ったのが別のスレッド
だったとしても、常にメインインタープリターの Python のメインスレッドで
実行されます。このためシグナルをスレッド間通信に使うことはできません。
代わりに "threading" モジュールが提供している同期プリミティブを利用で
きます。

また、メインインタープリターのメインスレッドだけが新しいシグナルハンド
ラを登録できます。

警告:

  Synchronization primitives such as "threading.Lock" should not be
  used within signal handlers.  Doing so can lead to unexpected
  deadlocks.


モジュールの内容
================

バージョン 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.

   Added in version 3.5.

class signal.Handlers

   "enum.IntEnum" collection of the constants "SIG_DFL" and "SIG_IGN".

   Added in version 3.5.

class signal.Sigmasks

   "enum.IntEnum" collection of the constants "SIG_BLOCK",
   "SIG_UNBLOCK" and "SIG_SETMASK".

   Availability: Unix.

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

   Added in version 3.5.

The variables defined in the "signal" module are:

signal.SIG_DFL

   二つある標準シグナル処理オプションのうちの一つです; 単純にシグナル
   に対する標準の関数を実行します。例えば、ほとんどのシステムでは、
   "SIGQUIT" に対する標準の動作はコアダンプと終了で、 "SIGCHLD" に対す
   る標準の動作は単にシグナルの無視です。

signal.SIG_IGN

   もう一つの標準シグナル処理オプションで、受け取ったシグナルを単に無
   視します。

signal.SIGABRT

   Abort signal from *abort(3)*.

signal.SIGALRM

   Timer signal from *alarm(2)*.

   Availability: Unix.

signal.SIGBREAK

   Interrupt from keyboard (CTRL + BREAK).

   Availability: Windows.

signal.SIGBUS

   Bus error (bad memory access).

   Availability: Unix.

signal.SIGCHLD

   Child process stopped or terminated.

   Availability: Unix.

signal.SIGCLD

   "SIGCHLD" のエイリアスです。

   Availability: not macOS.

signal.SIGCONT

   Continue the process if it is currently stopped

   Availability: Unix.

signal.SIGFPE

   Floating-point exception. For example, division by zero.

   参考:

     "ZeroDivisionError" is raised when the second argument of a
     division or modulo operation is zero.

signal.SIGHUP

   Hangup detected on controlling terminal or death of controlling
   process.

   Availability: Unix.

signal.SIGILL

   Illegal instruction.

signal.SIGINT

   Interrupt from keyboard (CTRL + C).

   Default action is to raise "KeyboardInterrupt".

signal.SIGKILL

   Kill signal.

   It cannot be caught, blocked, or ignored.

   Availability: Unix.

signal.SIGPIPE

   Broken pipe: write to pipe with no readers.

   Default action is to ignore the signal.

   Availability: Unix.

signal.SIGPROF

   Profiling timer expired.

   Availability: Unix.

signal.SIGQUIT

   Terminal quit signal.

   Availability: Unix.

signal.SIGSEGV

   Segmentation fault: invalid memory reference.

signal.SIGSTOP

   Stop executing (cannot be caught or ignored).

   Availability: Unix.

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.

   Added in version 3.11.

signal.SIGTERM

   Termination signal.

signal.SIGUSR1

   User-defined signal 1.

   Availability: Unix.

signal.SIGUSR2

   User-defined signal 2.

   Availability: Unix.

signal.SIGVTALRM

   Virtual timer expired.

   Availability: Unix.

signal.SIGWINCH

   Window resize signal.

   Availability: Unix.

signal.SIGXCPU

   CPU time limit exceeded.

   Availability: Unix.

SIG*

   All the signal numbers are defined symbolically.  For example, the
   hangup signal is defined as "signal.SIGHUP"; the variable names are
   identical to the names used in C programs, as found in
   "<signal.h>".  The Unix man page for '"signal"' lists the existing
   signals (on some systems this is *signal(2)*, on others the list is
   in *signal(7)*). Note that not all systems define the same set of
   signal names; only those names defined by the system are defined by
   this module.

signal.CTRL_C_EVENT

   "CTRL"+"C" キーストロークに該当するシグナル。このシグナルは
   "os.kill()" でだけ利用できます。

   Availability: Windows.

   Added in version 3.2.

signal.CTRL_BREAK_EVENT

   "CTRL"+"BREAK" キーストロークに該当するシグナル。このシグナルは
   "os.kill()" でだけ利用できます。

   Availability: Windows.

   Added in version 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

   "pthread_sigmask()" の *how* 引数に渡せる値で、シグナルがブロックさ
   れることを意味します。

   Added in version 3.3.

signal.SIG_UNBLOCK

   "pthread_sigmask()" の *how* 引数に渡せる値で、シグナルがブロック解
   除されることを意味します。

   Added in version 3.3.

signal.SIG_SETMASK

   "pthread_sigmask()" の *how* 引数に渡せる値で、シグナルが置換される
   ことを意味します。

   Added in version 3.3.

The "signal" module defines one exception:

exception signal.ItimerError

   背後の "setitimer()" または "getitimer()" 実装からエラーを通知する
   ために送出されます。無効なインタバルタイマーや負の時間が
   "setitimer()" に渡された場合、このエラーを予期してください。このエ
   ラーは "OSError" を継承しています。

   Added in version 3.3: このエラーは以前は "IOError" のサブタイプでし
   たが、 "OSError" のエイリアスになりました。

The "signal" module defines the following functions:

signal.alarm(time)

   *time* がゼロでない値の場合、この関数は *time* 秒後頃に "SIGALRM"
   をプロセスに送るように要求します。それ以前にスケジュールしたアラー
   ムはキャンセルされます (常に一つのアラームしかスケジュールできませ
   ん)。この場合、戻り値は以前に設定されたアラームシグナルが通知される
   まであと何秒だったかを示す値です。 *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.

   Added in version 3.8.

signal.valid_signals()

   Return the set of valid signal numbers on this platform.  This can
   be less than "range(1, NSIG)" if some signals are reserved by the
   system for internal use.

   Added in version 3.8.

signal.pause()

   シグナルを受け取るまでプロセスを一時停止します; その後、適切なハン
   ドラが呼び出されます。戻り値はありません。

   Availability: Unix.

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

   "sigwait()", "sigwaitinfo()", "sigtimedwait()" "sigpending()" も参
   照してください。

signal.raise_signal(signum)

   Sends a signal to the calling process. Returns nothing.

   Added in version 3.8.

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

   Send signal *sig* to the process referred to by file descriptor
   *pidfd*. Python does not currently support the *siginfo* parameter;
   it must be "None".  The *flags* argument is provided for future
   extensions; no flag values are currently defined.

   さらに詳しい情報についてはオンラインマニュアルページ
   *pidfd_send_signal(2)* を参照してください。

   Availability: Linux >= 5.1, Android >= "build-time" API level 31

   Added in version 3.9.

signal.pthread_kill(thread_id, signalnum)

   Send the signal *signalnum* to the thread *thread_id*, another
   thread in the same process as the caller.  The target thread can be
   executing any code (Python or not).  However, if the target thread
   is executing the Python interpreter, the Python signal handlers
   will be executed by the main thread of the main interpreter.
   Therefore, the only point of sending a signal to a particular
   Python thread would be to force a running system call to fail with
   "InterruptedError".

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

   If *signalnum* is 0, then no signal is sent, but error checking is
   still performed; this can be used to check if the target thread is
   still running.

   引数 "thread_id", "signalnum" を指定して 監査イベント
   "signal.pthread_kill" を送出します。

   Availability: Unix.

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

   "os.kill()" を参照してください。

   Added in version 3.3.

signal.pthread_sigmask(how, mask)

   これを呼び出すスレッドにセットされているシグナルマスクを取り出した
   り変更したりします。シグナルマスクは、呼び出し側のために現在どのシ
   グナルの配送がブロックされているかを示す集合 (set) です。呼び出し前
   のもとのシグナルマスクを集合として返却します。

   この関数の振る舞いは *how* に依存して以下のようになります。

   * "SIG_BLOCK": *mask* で指定されるシグナルが現時点のシグナルマスク
     に追加されます。

   * "SIG_UNBLOCK": *mask* で指定されるシグナルが現時点のシグナルマス
     クから取り除かれます。もともとブロックされていないシグナルをブロ
     ック解除しようとしても問題ありません。

   * "SIG_SETMASK": シグナルマスク全体を *mask* としてセットします。

   *mask* はシグナル番号の集合です (例えば {"signal.SIGINT",
   "signal.SIGTERM"})。全てのシグナルを含む全集合として
   "valid_signals()" を使うことが出来ます。

   呼び出しスレッドにセットされたシグナルマスクを問い合わせるには例え
   ば "signal.pthread_sigmask(signal.SIG_BLOCK, [])" とします。

   "SIGKILL" and "SIGSTOP" cannot be blocked.

   Availability: Unix.

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

   "pause()", "sigpending()", "sigwait()" も参照して下さい。

   Added in version 3.3.

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

   *which* で指定されたタイマー ("signal.ITIMER_REAL",
   "signal.ITIMER_VIRTUAL", "signal.ITIMER_PROF" のどれか) を、
   *seconds* 秒後と ("alarm()" と異なり、floatを指定できます)、それか
   ら (*interval* が0でなければ) *interval* 秒間隔で起動するように設定
   します。 *seconds* に0を指定すると、*which* で指定されたタイマーを
   クリアすることができます。

   インターバルタイマーが起動したとき、シグナルがプロセスに送られます
   。送られるシグナルは利用されたタイマーの種類に依存します。
   "signal.ITIMER_REAL" の場合は "SIGALRM" が、
   "signal.ITIMER_VIRTUAL" の場合は "SIGVTALRM" が、
   "signal.ITIMER_PROF" の場合は "SIGPROF" が送られます。

   以前の値が (delay, interval) のタプルとして返されます。

   無効なインターバルタイマーを渡すと "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 your program
   has registered a signal handler for is received, the signal number
   is written as a single byte into the fd.  If you haven't registered
   a signal handler for the signals you care about, then nothing will
   be written to the wakeup fd. This can be used by a library to
   wakeup a poll or select call, allowing the signal to be fully
   processed.

   The old wakeup fd is returned (or -1 if file descriptor wakeup was
   not enabled).  If *fd* is -1, file descriptor wakeup is disabled.
   If not -1, *fd* must be non-blocking.  It is up to the library to
   remove any bytes from *fd* before calling poll or select again.

   スレッドが有効な場合、この関数は メインインタープリターのメインスレ
   ッド からしか実行できません。それ以外のスレッドからこの関数を実行し
   ようとすると "ValueError" 例外が発生します。

   There are two common ways to use this function. In both approaches,
   you use the fd to wake up when a signal arrives, but then they
   differ in how they determine *which* signal or signals have
   arrived.

   In the first approach, we read the data out of the fd's buffer, and
   the byte values give you the signal numbers. This is simple, but in
   rare cases it can run into a problem: generally the fd will have a
   limited amount of buffer space, and if too many signals arrive too
   quickly, then the buffer may become full, and some signals may be
   lost. If you use this approach, then you should set
   "warn_on_full_buffer=True", which will at least cause a warning to
   be printed to stderr when signals are lost.

   In the second approach, we use the wakeup fd *only* for wakeups,
   and ignore the actual byte values. In this case, all we care about
   is whether the fd's buffer is empty or non-empty; a full buffer
   doesn't indicate a problem at all. If you use this approach, then
   you should set "warn_on_full_buffer=False", so that your users are
   not confused by spurious warning messages.

   バージョン 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* にします。
   *handler* は二つの引数 (下記参照) を取る呼び出し可能な Python オブ
   ジェクトか、 "signal.SIG_IGN" あるいは "signal.SIG_DFL" といった特
   殊な値にすることができます。以前に使われていたシグナルハンドラが返
   されます (上記の "getsignal()" の記述を参照してください)。 (さらに
   詳しい情報については Unix マニュアルページ *signal(2)* を参照してく
   ださい。)

   スレッドが有効な場合、この関数は メインインタープリターのメインスレ
   ッド からしか実行できません。それ以外のスレッドからこの関数を実行し
   ようとすると "ValueError" 例外が発生します。

   *handler* は二つの引数とともに呼び出されます: シグナル番号、および
   現在のスタックフレーム ("None" またはフレームオブジェクト; フレーム
   オブジェクトについての記述は 標準型の階層における説明 か、
   "inspect" モジュールの属性の説明を参照してください)。

   On Windows, "signal()" can only be called with "SIGABRT", "SIGFPE",
   "SIGILL", "SIGINT", "SIGSEGV", "SIGTERM", or "SIGBREAK". A
   "ValueError" will be raised in any other case. Note that not all
   systems define the same set of signal names; an "AttributeError"
   will be raised if a signal name is not defined as "SIG*" module
   level constant.

signal.sigpending()

   呼び出しスレッドで配送が保留されているシグナル (つまり配送がブロッ
   クされている間に発生したシグナル) の集合を調べます。保留中のシグナ
   ルの集合を返します。

   Availability: Unix.

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

   "pause()", "pthread_sigmask()", "sigwait()" も参照して下さい。

   Added in version 3.3.

signal.sigwait(sigset)

   *sigset* 集合で指定されたシグナルのうちどれか一つが届くまで呼び出し
   スレッドを一時停止します。この関数はそのシグナルを受け取ると (それ
   を保留シグナルリストから取り除いて) そのシグナル番号を返します。

   Availability: Unix.

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

   "pause()", "pthread_sigmask()", "sigpending()", "sigwaitinfo()",
   "sigtimedwait()" も参照して下さい。

   Added in version 3.3.

signal.sigwaitinfo(sigset)

   Suspend execution of the calling thread until the delivery of one
   of the signals specified in the signal set *sigset*.  The function
   accepts the signal and removes it from the pending list of signals.
   If one of the signals in *sigset* is already pending for the
   calling thread, the function will return immediately with
   information about that signal. The signal handler is not called for
   the delivered signal. The function raises an "InterruptedError" if
   it is interrupted by a signal that is not in *sigset*.

   The return value is an object representing the data contained in
   the "siginfo_t" structure, namely: "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()" も参照して下さい。

   Added in version 3.3.

   バージョン 3.5 で変更: The function is now retried if interrupted
   by a signal not in *sigset* and the signal handler does not raise
   an exception (see **PEP 475** for the rationale).

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()" も参照して下さい。

   Added in version 3.3.

   バージョン 3.5 で変更: The function is now retried with the
   recomputed *timeout* if interrupted by a signal not in *sigset* and
   the signal handler does not raise an exception (see **PEP 475** for
   the rationale).


使用例
======

以下は最小限のプログラム例です。この例では "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


Note on SIGPIPE
===============

Piping output of your program to tools like *head(1)* will cause a
"SIGPIPE" signal to be sent to your process when the receiver of its
standard output closes early.  This results in an exception like
"BrokenPipeError: [Errno 32] Broken pipe".  To handle this case, wrap
your entry point to catch this exception as follows:

   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()

Do not set "SIGPIPE"'s disposition to "SIG_DFL" in order to avoid
"BrokenPipeError".  Doing that would cause your program to exit
unexpectedly whenever any socket connection is interrupted while your
program is still writing to it.


Note on Signal Handlers and Exceptions
======================================

If a signal handler raises an exception, the exception will be
propagated to the main thread and may be raised after any *bytecode*
instruction. Most notably, a "KeyboardInterrupt" may appear at any
point during execution. Most Python code, including the standard
library, cannot be made robust against this, and so a
"KeyboardInterrupt" (or any other exception resulting from a signal
handler) may on rare occasions put the program in an unexpected state.

To illustrate this issue, consider the following code:

   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()

For many programs, especially those that merely want to exit on
"KeyboardInterrupt", this is not a problem, but applications that are
complex or require high reliability should avoid raising exceptions
from signal handlers. They should also avoid catching
"KeyboardInterrupt" as a means of gracefully shutting down.  Instead,
they should install their own "SIGINT" handler. Below is an example of
an HTTP server that avoids "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...")
