Python シグナルによるプロセスの再起動

サーバシステムなどでは、設定ファイルの変更に伴う再起動にあたり、ハングアップシグナル(SIGHUP)を送り、execコマンドで上書きするというやり方がよく使われています。
今回はこの方法について学んでいきます。
ハングアップシグナルとは
前回では、シグナルというものを使いユーザやカーネルがプロセスに対して通知を送信できることについて学びました。
今回利用するハングアップシグナル(SIGHUP)は、元々端末を切断するために使っていたものですが、現在ではログアウトやデーモンのリセットなどに使用したりするためのシグナルです。
execコマンドとは
execコマンドとは、自分自身(プロセス)を新しいプログラムで上書きするコマンドになり、そのためプロセスIDはそのままになります。
なお、execコマンドにはexec*()といった渡す引数によって末尾が異なるものがいくつかあり、excecve()のみシステムコールでそれ以外はライブラリ関数になります。
例えば、Apacheなどでreloadをした場合には、関連する子プロセスをすべて終了させ、親プロセス自信を上書きし、設定を再読み込みして再起動します。
スクリプトの作成
以下のスクリプトは、Linuxネットワークプログラミングバイブルに記載されているシグナルテスト用のプログラムを、Pythonで書き直したものになります。
なお、C言語ではsignal()とsigaction()の2つのAPIによって、プロセスがシグナルを補足した際の挙動を変えることができますが、signal()はOSによってはシステムコールの再起動をするなど、実装上の注意点や移植性に欠ける点があるため、sigaction()を使用するのが一般的です。
# signaltest.py
import os
import signal
import time
import sys
def sig_hangup_handler(sig, frame):
sys.stderr.write('sig_hangup_handler({})\n'.format(sig))
try:
sys.stderr.write('restarting...\n')
os.execve('/usr/bin/python3', ['/usr/bin/python3', 'signaltest.py'], {'PATH':'test'})
except OSError as e:
sys.stderr.write("execve():{}\n".format(e))
os._exit(1)
sys.stderr.write('PID:{}\n'.format(os.getpid()))
signal.signal(signal.SIGHUP, sig_hangup_handler)
i = 0
while True:
sys.stderr.write("count={}\n".format(i))
i += 1
time.sleep(5)
SIGHUPを補足したらexecve()でプログラム自信を上書きし再起動させます。
動作確認
それでは、上記で作成したスクリプトを実行し、実行後に別ターミナルからkillコマンドでハングアップシグナルを送信します。
$ python3 signaltest.py PID:1825 count=0 count=1 count=2 count=3 sig_hangup_handler(1) <-killコマンドでSIGHUPを送信 restarting... PID:1825 count=0 count=1 count=2 count=3 sig_hangup_handler(1) <-killコマンドでSIGHUPを送信 restarting... PID:1825 count=0 count=1 count=2 count=3 Killed <-killコマンドでSIGKILLを送信
ハングアップシグナルを受け取ったタイミングでハンドラを実行し、プログラムが上書きされカウントが再び開始されますが、プロセスIDが変化していないことが確認できます。