TL;DR
- Herokuでは再起動時にSIGTERMを送信して、10秒間応答がなければSIGKILLでプロセスを殺すよ
- Pythonでは標準に含まれる
signal
というライブラリでシグナル関連の処理をするよ
結論
import signal
def signal_handler(signal_number, frame):
# ここにシグナルを受け取ったときに実行したい処理を書く
pass
signal.signal(signal.SIGTERM, receive_signal)
Motivation
Herokuでは、おおよそ24時間毎にdynoが自動で再起動されてしまいます。
例えばdynoがデータベースサーバーと常時通信する必要がある場合、急に落ちるとデータベースに不整合が生じる可能性があります。
これを防ぐため、dynoの再起動時に特定の処理を実行するようにする必要がありました。
今回はdynoの再起動時に発行されるシグナルを用いてそれを実現することにしました。
そもそもシグナルとは
シグナル(英: signal)とは、Unix系(POSIX標準に類似の)OSにおける、限定的なプロセス間通信の形式を使って、プロセスに対し、非同期で、イベントの発生を伝える機構である。シグナルが送信された際、OSは宛先プロセスの正常な処理の流れに割り込む。
シグナル - Wikipedia より
要するに、プロセスの外で発生したイベントに対応するために、プロセスに伝わる信号のことです。
例えばCtrl + Cでプロセスを終了するときや、タスクマネージャー(アクティビティーモニター)でプロセスを終了する時などにも、プロセスの終了を通知するシグナルが送信されます。
シグナル一覧とその意味
重要なものだけ紹介します。
シグナル名 | シグナル番号 | 詳細 |
---|---|---|
SIGINT | 2 | Ctrl+Cが入力された時 |
SIGKILL | 9 | プロセスの強制終了 |
SIGTERM | 15 | プロセスの終了、kill コマンド |
それぞれのシグナルには、規定の処理が設定されています。
今回はそれを変更することでハンドリングを実現します。
しかし、SIGKILLについては、あくまで「強制終了」なので、既定の処理云々以前に問答無用でプロセスが消えます。
Ctrl+Cを入力しても一発では落ちないアプリも、これを使っていたわけです。
ソースコードの解説
import signal
def signal_handler(signal_number, frame):
# ここにシグナルを受け取ったときに実行したい処理を書く
pass
signal.signal(signal.SIGTERM, receive_signal)
signal
ライブラリ内のsignal.signal()
という関数を用いてハンドリングを行います。
第一引数にはハンドルしたいSignals
型の引数が入り、signal.<シグナル名>
の形式になっています。
今回の場合はSIGTERM
です。
第二引数には実際に呼び出したい関数の関数オブジェクトを渡します。
なお、呼び出される関数は2つの引数を受け取ります。
第一引数はシグナルの番号受け取り、第二引数にはフレームを受け取ります。
ここではフレームは無視しても問題ないでしょう。