タイムラインに次のようなものが流れてきまして、Ruby と同じ原因かどうかわかりませんが PHP でもそうなるなーと思ったので試しました。
親プロセスに送ったシグナルが子プロセスに通知されるように見えるのは、やっぱりRubyの問題っぽいなぁ。システムコールトレース取っても、子プロセスには実際にはシグナルは来てない。シグナル受信と処理にタイムラグがあって、その間に fork すると子プロセス側で処理しちゃってる感じ?
— とみたまさひろ (@tmtms) 2014, 9月 22
PHP の pcntl_signal
で登録するシグナルハンドラは、実は本当のシグナルハンドラとして実行されるわけではなく、受けたシグナルを一旦 PHP の側で(というか pcntl 拡張の中で)キューに保存して pcntl_signal_dispatch
の呼び出し時に普通に PHP のコールバック関数を呼ぶような感じで呼ばれます(declare(ticks=1)
していれば pcntl_signal_dispatch
が無くても勝手に呼ばれます)。
なので本来であればシグナルハンドラではできないような処理も記述できるのですが、次のようなコードだと pcntl_signal_dispatch
を呼んでから pcntl_fork
するまでに受けたシグナルは親と子の両方で処理されます。pcntl 拡張の中で保持されているシグナルのキューごと fork されるからです。
<?php
pcntl_signal(SIGHUP, function () {
$pid = posix_getpid();
echo "SIGHUP [$pid]\n";
});
$pid = posix_getpid();
echo "I am [$pid]\n";
echo "ここで HUP を打つと親だけ処理する\n";
sleep(10);
pcntl_signal_dispatch();
echo "ここで HUP を打つと親と子の両方が処理する\n";
sleep(10);
$pid = pcntl_fork();
if ($pid < 0) {
exit("!!!fork");
} else if ($pid > 0) {
// 親
echo "Forked child process [$pid]\n";
for (;;) {
pcntl_signal_dispatch();
sleep(1);
}
} else {
// 子
for (;;) {
pcntl_signal_dispatch();
sleep(1);
}
}
解決方法
次のように fork の前に pcntl_sigprocmask
でシグナルをブロックし、pcntl_signal_dispatch
でシグナルのキューを空にしたうえで pcntl_fork
し、その後 pcntl_sigprocmask
でブロックを解除するといいのではないでしょうか(ブロック中に受けたシグナルはブロックを解除したときに処理されます)。
<?php
pcntl_signal(SIGHUP, function () {
$pid = posix_getpid();
echo "SIGHUP [$pid]\n";
});
$pid = posix_getpid();
echo "I am [$pid]\n";
echo "ここで HUP を打つと親だけ処理する\n";
sleep(10);
pcntl_sigprocmask(SIG_BLOCK, [SIGHUP]);
pcntl_signal_dispatch();
echo "ここで HUP を打ってもブロックされるのでブロックが解除されるまでペンディングされる\n";
sleep(10);
$pid = pcntl_fork();
pcntl_sigprocmask(SIG_UNBLOCK, [SIGHUP]);
if ($pid < 0) {
exit("!!!fork");
} else if ($pid > 0) {
// 親
echo "Forked child process [$pid]\n";
for (;;) {
pcntl_signal_dispatch();
sleep(1);
}
} else {
// 子
for (;;) {
pcntl_signal_dispatch();
sleep(1);
}
}