[PHP]execでバックグラウンド実行するときの落とし穴に落ちた。 nohup!nohup!

PHPで、exec関数(またはshell_exec, system_exec)を使って他のコマンドをバックグランド実行するとき、どうやってますか?

私は今までこのように書いていたのですが、大きな大きな落とし穴があることがわかりました。
# バックグランドでcommandを実行
exec('command > /dev/null &');
このコマンドで重い処理をやっていたのですが、なぜか毎日ある時刻になると突然止まるのです。
いろいろ調べてみたら面白いことがわかりました。

犯人はcron.dailyとlogrotateでした。

logrotateでコマンドが中断されてしまうメカニズム

そのサーバではcronの設定が下記のようになっており、
/etc/crontab
27 0 * * * root run-parts /etc/cron.daily
毎日0:27 に/etc/cron.daily/*が呼び出される設定になっていました。
そこには/etc/cron.daily/loglotateという実行コマンドがあります。

そしてlogrotateの設定はというと、
/etc/logrotate.d/httpd
/var/log/httpd/*log {
    daily
    rotate 30
    compress
    missingok
    notifempty
    sharedscripts
    postrotate
        /sbin/service httpd reload > /dev/null 2>/dev/null || true
    endscript
}
となっておりました。

service httpd reloadはどうなっているのかというと、
service httpd の実体は /etc/init.d/httpd なのでそれを見てみます。
/etc/init.d/httpd
reload() {
    echo -n $"Reloading $prog: "
    if ! LANG=$HTTPD_LANG $httpd $OPTIONS -t >&/dev/null; then
        RETVAL=$?
        echo $"not reloading due to configuration syntax error"
        failure $"not reloading $httpd due to configuration syntax error"
    else
        killproc -p ${pidfile} $httpd -HUP;
        RETVAL=$?
    fi
    echo
}
このようにApacheに対してHUPシグナルが送られます。

ApacheにHUPシグナルを送るとどうなるのかというと、
"停止と再起動 - Apache HTTP サーバ"

HUP あるいは restart シグナルを親プロセスに送ると、 TERM と同様に子プロセスを kill しますが、 親プロセスは終了しません。 設定ファイルを再読込して、ログファイル全てを開き直します。 その後、新しい子プロセスを起動して応答を続けます
子プロセスをkillしてしまうようです。(ここでkillと言ってるのはHUPシグナルのことでしょうか?)

要約すると、「execで外部コマンドをバックグランドで走らせてる間にlogrotateが実行されると、外部コマンドが強制終了してしまう」ということです。
何とも痛い面白い仕組みですね。

回避方法

コマンド実行の際に、頭に'nohup'を付ける。
# バックグランドでcommandを実行
exec('command > /dev/null &');
↓
exec('nohup command > /dev/null &');
または、
logrotateの処理をreloadではなくgracefulにする。
/sbin/service httpd reload
↓
/sbin/service httpd graceful

私が実験してみたところ、どちらか片方だけでも回避できましたが、両方やっておくのが安全だと思います。

参考

カテゴリ:

人気記事