fork 直前にシグナルを受けると親と子の両方が受ける例

  • 5
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

タイムラインに次のようなものが流れてきまして、Ruby と同じ原因かどうかわかりませんが PHP でもそうなるなーと思ったので試しました。

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);
    }
}