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

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


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

一般的なルール

signal.signal() 関数で、シグナルを受信したときに実行するハンドラーを定義できます。 デフォルトでは少数のハンドラーが設定されています。 SIGPIPE は無視されます。 これにより、パイプやソケットへの書き込みエラーを通常の Python 例外として報告できます。 親プロセスが処理方法を変更していなければ、SIGINT は KeyboardInterrupt 例外に変換されます。

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

WebAssembly プラットフォームではシグナルをエミュレートするため、動作が異なります。 これらのプラットフォームでは一部の関数とシグナルを利用できません。

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

Python のシグナルハンドラーは、低水準の (C の) シグナルハンドラー内では実行されません。 低水準のシグナルハンドラーはフラグを設定し、virtual machine に対して、後の時点 (例えば次の bytecode 命令の実行時) に対応する Python のシグナルハンドラーを実行するよう通知します。 この仕組みにより、次のような動作になります。

  • C コードの不正な操作によって生じる SIGFPE や SIGSEGV などの同期的なエラーを捕捉しても、ほとんど意味がありません。 シグナルハンドラーから C コードに戻ると同じシグナルが再び発生する可能性が高く、Python が停止したように見えるためです。 Python 3.3 以降では faulthandler モジュールで同期的なエラーを報告できます。

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

  • ハンドラーが例外を送出すると、メインスレッドではその時点で実行していた処理とは無関係に例外が発生します。 詳しくは 後述の注意事項 を参照してください。

シグナルとスレッド

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

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

警告

threading.Lock などの同期プリミティブをシグナルハンドラー内で使わないでください。 予期しないデッドロックを引き起こすおそれがあります。

モジュールの内容

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

signal モジュールは三つの列挙型を定義します。

class signal.Signals

SIG* 定数と CTRL_* 定数を集めた enum.IntEnum です。

Added in version 3.5.

class signal.Handlers

SIG_DFL と SIG_IGN の定数を集めた enum.IntEnum です。

Added in version 3.5.

class signal.Sigmasks

SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK の定数を集めた enum.IntEnum です。

利用可能な環境: Unix.

詳細は sigprocmask(2) と pthread_sigmask(3) のマニュアルページを参照してください。

Added in version 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 のエイリアスです。

利用可能な環境: not 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) のマニュアルページを参照してください。

Added in version 3.11.

signal.SIGTERM

終了シグナル。

signal.SIGUSR1

ユーザー定義シグナル 1。

signal.SIGUSR2

ユーザー定義シグナル 2。

signal.SIGVTALRM

仮想タイマーの満了。

signal.SIGWINCH

ウィンドウサイズ変更シグナル。

signal.SIGXCPU

CPU 時間の制限超過。

SIG*

すべてのシグナル番号はシンボルとして定義されています。 例えばハングアップシグナルは signal.SIGHUP として定義されています。 変数名は <signal.h> にある C プログラム用の名前と同じです。 Unix の signal マニュアルページには、利用可能なシグナルが列挙されています (システムによって signal(2) または signal(7) に記載されています)。 すべてのシステムが同じシグナル名を定義しているわけではなく、このモジュールで定義されるのはそのシステムで定義されている名前だけです。

signal.CTRL_C_EVENT

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

Added in version 3.2.

signal.CTRL_BREAK_EVENT

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

Added in version 3.2.

signal.NSIG

最大のシグナル番号に 1 を足した値です。 有効なシグナル番号を取得するには valid_signals() を使ってください。

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.

signal モジュールは一つの例外を定義します。

exception signal.ItimerError

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

Added in version 3.3: このエラーは以前は IOError のサブタイプでした。 現在、IOError は OSError のエイリアスです。

signal モジュールは以下の関数を定義します。

signal.alarm(time)

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

利用可能な環境: Unix.

詳細は 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 の説明を返します (例えば SIGINT なら "Interrupt")。 signalnum に説明がなければ None を返します。 signalnum が無効な場合は ValueError を送出します。

Added in version 3.8.

signal.valid_signals()

このプラットフォームで有効なシグナル番号の集合を返します。 システムが内部使用のために予約しているシグナルがある場合は、range(1, NSIG) より少なくなることがあります。

Added in version 3.8.

signal.pause()

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

利用可能な環境: Unix.

詳細は signal(2) のマニュアルページを参照してください。

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

signal.raise_signal(signum)

呼び出し元のプロセスにシグナルを送ります。 戻り値はありません。

Added in version 3.8.

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

ファイル記述子 pidfd が指すプロセスにシグナル sig を送ります。 Python は現在 siginfo 引数に対応していないため、None にしなければなりません。 flags 引数は将来の拡張用で、現在定義されているフラグ値はありません。

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

利用可能な環境: 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.

thread_id に指定する値は threading.get_ident() または threading.Thread オブジェクトの ident 属性で取得できます。

signalnum が 0 の場合、シグナルは送られませんがエラーチェックは行われます。 これを使って対象のスレッドがまだ実行中かどうかを確認できます。

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

利用可能な環境: Unix.

詳細は pthread_kill(3) のマニュアルページを参照してください。

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 と SIGSTOP はブロックできません。

利用可能な環境: Unix.

詳細は sigprocmask(2) と pthread_sigmask(3) のマニュアルページを参照してください。

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

Added in version 3.3.

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

which で指定したインターバルタイマー (signal.ITIMER_REAL、signal.ITIMER_VIRTUAL、signal.ITIMER_PROF のいずれか) を、seconds 秒後に発火するよう設定します (alarm() とは異なり、マイクロ秒単位に切り上げます)。 その後は interval がゼロでなければ interval 秒ごとに発火します。 seconds をゼロにすると which で指定したインターバルタイマーを解除できます。

インターバルタイマーが設定時間に達したとき、シグナルがプロセスに送られます。 送られるシグナルは利用されたタイマーの種類に依存します。 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 に設定します。 プログラムがハンドラーを登録したシグナルを受信すると、その番号が 1 バイトとしてこのファイル記述子に書き込まれます。 対象のシグナルにハンドラーを登録していなければ何も書き込まれません。 ライブラリはこの仕組みで poll や select の待機を解除し、シグナルを最後まで処理できます。

以前の待機解除用ファイル記述子を返します (有効になっていなければ -1)。 fd が -1 の場合はファイル記述子による待機解除を無効にします。 -1 でない場合、fd は非ブロッキングでなければなりません。 poll や select を再び呼ぶ前に fd のバイトをすべて読み出すのはライブラリの責任です。

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

この関数には一般的な使い方が二つあります。 どちらもシグナルの到着時にファイル記述子で待機を解除しますが、どの シグナルが到着したかを判別する方法が異なります。

一つ目の方法では、ファイル記述子のバッファからデータを読み出し、そのバイト値からシグナル番号を取得します。 これは単純ですが、まれに問題が起こります。 通常、ファイル記述子のバッファ容量には上限があるため、シグナルが短時間に大量に到着するとバッファがいっぱいになり、一部のシグナルを取りこぼす可能性があります。 この方法を使う場合は warn_on_full_buffer=True を設定してください。 そうすれば、少なくともシグナルを取りこぼしたときに標準エラー出力に警告が表示されます。

二つ目の方法では、ファイル記述子を待機解除のため だけ に使い、実際のバイト値は無視します。 この場合はバッファが空かどうかだけを見ており、バッファがいっぱいになっても問題はありません。 不要な警告メッセージで利用者を混乱させないよう、この方法では warn_on_full_buffer=False を設定してください。

バージョン 3.5 で変更: Windowsで、この関数はソケットハンドルをサポートするようになりました。

バージョン 3.7 で変更: warn_on_full_buffer 引数が追加されました。

signal.siginterrupt(signalnum, flag)

システムコールのリスタートの動作を変更します。 flag が False の場合、 signalnum シグナルに中断されたシステムコールは再実行されます。それ以外の場合、システムコールは中断されます。戻り値はありません。

利用可能な環境: Unix.

詳細は siginterrupt(3) のマニュアルページを参照してください。

signal() でシグナルハンドラーを登録すると、対象のシグナルに対して flag を真にして siginterrupt() が暗黙に呼び出され、システムコールを中断する動作にリセットされることに注意してください。

signal.signal(signalnum, handler)

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

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

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

Windows では signal() に指定できるのは SIGABRT、SIGFPE、SIGILL、SIGINT、SIGSEGV、SIGTERM、SIGBREAK だけです。 他の値では ValueError が送出されます。 すべてのシステムが同じシグナル名を定義しているわけではなく、SIG* というモジュールレベルの定数が定義されていない名前を参照すると AttributeError が送出されます。

signal.sigpending()

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

利用可能な環境: Unix.

詳細は sigpending(2) のマニュアルページを参照してください。

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

Added in version 3.3.

signal.sigwait(sigset)

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

利用可能な環境: Unix.

詳細は sigwait(3) のマニュアルページを参照してください。

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.

戻り値は siginfo_t 構造体に含まれるデータ、すなわち si_signo、si_code、si_errno、si_pid、si_uid、si_status、si_band を表すオブジェクトです。

利用可能な環境: Unix.

詳細は sigwaitinfo(2) のマニュアルページを参照してください。

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

Added in version 3.3.

バージョン 3.5 で変更: sigset に含まれないシグナルで中断され、そのハンドラーが例外を送出しなかった場合、関数を再試行するようになりました (理由は PEP 475 を参照)。

signal.sigtimedwait(sigset, timeout)

sigwaitinfo() と同様ですが、タイムアウトを指定する timeout 引数を追加で受け取ります。 timeout に 0 を指定するとポーリングを行います。 タイムアウトした場合は None を返します。

利用可能な環境: Unix.

詳細は sigtimedwait(2) のマニュアルページを参照してください。

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

Added in version 3.3.

バージョン 3.5 で変更: sigset に含まれないシグナルで中断され、そのハンドラーが例外を送出しなかった場合、timeout を再計算して関数を再試行するようになりました (理由は PEP 475 を参照)。

バージョン 3.15 で変更: timeout に、整数や浮動小数点数だけでなく任意の実数を渡せるようになりました。

使用例

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

# シグナルハンドラーと 5 秒後のアラームを設定する
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)

# この open() は無期限に待機する可能性がある
fd = os.open('/dev/ttyS0', os.O_RDWR)

signal.alarm(0)          # アラームを解除する

SIGPIPE についての注意

プログラムの出力を head(1) などのツールにパイプで渡していて、標準出力の受け取り側が先に閉じると、プロセスに SIGPIPE シグナルが送られます。 この結果、BrokenPipeError: [Errno 32] Broken pipe のような例外が発生します。 この状況に対処するには、次のようにエントリーポイントの処理を囲んで例外を捕捉します。

import os
import sys

def main():
    try:
        # 大量の出力を模擬する (このループを実際のコードに置き換える)
        for x in range(10000):
            print("y")
        # この try ブロック内で SIGPIPE が発生するよう、
        # ここで出力をフラッシュする。
        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)  # EPIPE では Python はエラーコード 1 で終了する

if __name__ == '__main__':
    main()

BrokenPipeError を避けるために SIGPIPE の処理方法を SIG_DFL に設定しないでください。 そうすると、ソケットへの書き込み中に接続が切れるたびに、プログラムが予期せず終了してしまいます。

シグナルハンドラーと例外についての注意

シグナルハンドラーが例外を送出すると、その例外はメインスレッドに伝播し、どの bytecode 命令の実行後にも発生する可能性があります。 特に KeyboardInterrupt は実行中のどの時点でも発生し得ます。 標準ライブラリを含むほとんどの Python コードはこれに対して堅牢にできないため、KeyboardInterrupt (またはシグナルハンドラーによる他の例外) によって、まれにプログラムが予期しない状態になることがあります。

この問題を示す例として、次のコードを考えてください。

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 ハンドラーを登録してください。 以下は KeyboardInterrupt を発生させない HTTP サーバーの例です。

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