PHP からバックグラウンド起動させたプロセスを Apache の停止や再起動後も続行させる ← 失敗

mod_php(Apache/PHP)で、ページの表示時にバックグラウンドで別プロセスを起動して並列に処理したい場合、次のように書くことが出来ます。

<?php
system('/bin/ping 127.0.0.1 1>/dev/null 2>&1 &');


ただし、この方法だと Apache の子プロセスの停止や再起動時に、起動したプロセス(上の例なら ping コマンド)に HUP や TERM や USR1 が投げられるため、起動したプロセスの方でそのシグナルを処理していなければ強制終了されます。


そこで、次のようにデーモン化のスクリプトを挟んでプロセスを親から切り離し、Apache の停止や再起動とは無関係に処理を続行できるようにしました。

daemon.php
<?php
$pid = pcntl_fork();

if($pid == 0)
{
	posix_setsid();
	
	$pid = pcntl_fork();
	
	if($pid == 0)
	{
		pcntl_exec('/bin/ping', array('127.0.0.1'));
	}
}


Webページ側の PHP では次のように呼び出します。

<?php
system('/usr/bin/php /path/to/daemon.php 1>/dev/null 2>&1`');


・・・と思ったのですが、apache の stop → start で上手く行かなくなりました。。。


上の方法で ping コマンドをバックグラウンドで実行させたあとに apachectl stop → apachectl start すると次の通りエラーになりました。

(98)Address already in use: make_sock: could not bind to address 0.0.0.0:443
no listening sockets available, shutting down


lsof -i:443 で見てみると・・・

ping    14860 apache    3u  IPv4 639201      0t0  TCP *:https (LISTEN)


バックグラウンドで実行中の ping がリッスンソケットを開きっぱなしにしているようです。


Linux の fork はファイルディスクリプタ(ソケット含む)を複製するため、fork 後に子プロセスでそれらを閉じなければ、親プロセスが終了した後も意図せずファイルディスクリプタが残ったままになります。標準出力などは system() 実行時のリダイレクションでなんとかなっているようですが、Apache のリッスンソケットは PHP 側ではどうしようもありませんね・・・


Apache のスーパープロセスが子プロセスを fork した後に子プロセスに複製されたリッスンソケットは全部閉じとけよ! っと思いましたが、prefork なので accept は Apache 子プロセス側で行なっているため閉じれないでしょね。