3

仕様

再生成されたプロセスがシグナルを処理していないときに、再生成する前に処理が正しく機能しているとき、PHP に問題があります。コードを非常に基本的なものに絞り込みました。

declare(ticks=1);

register_shutdown_function(function() {
    if ($noRethrow = ob_get_contents()) {
        ob_end_clean();
        exit;
    }
    system('/usr/bin/nohup /usr/bin/php '.__FILE__. ' 1>/dev/null 2>/dev/null &');
});

function handler($signal)
{
    switch ($signal) {
        case SIGTERM:
            file_put_contents(__FILE__.'.log', sprintf('Terminated [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
            ob_start();
            echo($signal);
            exit;
        case SIGCONT:
            file_put_contents(__FILE__.'.log', sprintf('Restarted [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
            exit;
    }
}

pcntl_signal(SIGTERM, 'handler');
pcntl_signal(SIGCONT, 'handler');

while(1) {
    if (time() % 5 == 0) {
        file_put_contents(__FILE__.'.log', sprintf('Idle [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
    }
    sleep(1);
}

ご覧のとおり、次のことを行います。

  • プロセスを再起動するシャットダウン関数を登録します(親プロセスが終了したときnohupに無視するため)SIGHUP
  • pcntl_signal()forSIGTERMおよびを介してハンドラーを登録しSIGCONTます。最初はプロセスが終了したというメッセージを記録するだけですが、2番目はプロセスの再生成につながります。これはob_*関数で実現されるため、フラグを渡すには、シャットダウン関数で何を行う必要がありますか - 終了または再生成のいずれかです。
  • スクリプトが「生きている」という情報をログ ファイルに記録します。

何が起こっている

だから、私はスクリプトを開始しています:

/usr/bin/nohup /usr/bin/php script.php 1>/dev/null 2>/dev/null &

次に、ログ ファイルに次のようなエントリがあります。

Idle [ppid=7171] [pid=8849]
Idle [ppid=7171] [pid=8849]

たとえば、私は次のようにしますkill 8849

Terminated [ppid=7171] [pid=8849]

したがって、正常に処理されSIGTERMます (そしてスクリプトは実際に終了します)。ここで、代わりに を実行するkill -18 8849と、(18 は の数値SIGCONT)が表示されます。

Idle [ppid=7171] [pid=8849]
Restarted [ppid=7171] [pid=8849]
Idle [ppid=1] [pid=8875]
Idle [ppid=1] [pid=8875]

したがって、最初はSIGCONT正しく処理され、次の「アイドル」メッセージから判断すると、新しく生成されたスクリプトのインスタンスはうまく機能しています。

更新#1:(ppid=1したがって、initグローバルプロセス)および孤立プロセスのシグナル処理について考えていましたが、そうではありません。これは、orphan ( ) プロセスが理由ではないことを示していますppid=1アプリを制御してワーカーを起動すると、system()ワーカーが自分自身を再生成するのと同じように、コマンドで呼び出します。ただし、制御アプリがワーカーを呼び出した後、ppid=1シグナルを正しく受信して応答しますが、ワーカーが自分自身を再生成した場合、新しいコピーはそれらに応答しませんSIGKILLそのため、ワーカーが自分自身を再生成する場合にのみ問題が発生します。

更新 #2 : で何が起こっているのかを分析しようとしましたstrace。さて、ここに2つのブロックがあります。

  1. ワーカーがまだ再生成されていない場合-strace の出力。行4と を5SIGCONT見てください。これはkill -18、プロセスに送信するときです。そして、すべてのチェーンをトリガーします: ファイルへの書き込み、system()現在のプロセスの呼び出しと終了。
  2. ワーカーがすでにそれ自体で再生成されている場合 - strace の出力。ここで、行89見てください- それらは を受け取った後に現れましたSIGCONT。1 つ目: プロセスがまだ何らかの形でシグナルを受信して​​いるように見え、2 つ目はシグナルを無視することです。アクションは実行されませんでしたが、送信されたシステムによってプロセスが通知されましたSIGCONT。なぜプロセスがそれを無視するのか - 問題です (ユーザー ハンドラのインストールがSIGCONT失敗した場合、プロセスは終了せずに実行を終了する必要があるため)。に関してはSIGKILL、すでにリスポーンされたワーカーの出力は次のようになります。

    nanosleep({1, 0},  <unfinished ...>
    +++ killed by SIGKILL +++
    

これは、そのシグナルが受信され、本来すべきことを行ったことを示しています。

問題

SIGTERMプロセスがリスポーンされるため、 にも にも反応しませんSIGCONT。ただし、それを終了することはまだ可能ですSIGKILL(したがって、kill -9 PID実際にプロセスを終了します)。たとえば、両方の上のプロセスの場合、何もkill 8875kill -18 8875ません (プロセスはシグナルを無視し、メッセージを記録し続けます)。

ただし、シグナルの登録が完全に失敗しているとは言いません-少なくとも再定義するためですSIGTERM(通常は終了につながりますが、この場合は無視されます)。またppid = 1、何か間違ったことを指しているのではないかと思いますが、今ははっきりとは言えません。

また、他の種類のシグナルを試しました(実際、シグナルコードが何であるかは関係ありませんでした。結果は常に同じでした)

質問

そのような行動の理由は何ですか?プロセスを再生成する方法は正しいですか? そうでない場合、新しく生成されたプロセスがユーザー定義のシグナルハンドラーを正しく使用できるようにする他のオプションは何ですか?

4

2 に答える 2

1

解決策: 最終的にstrace、問題の理解に役立ちました。これは次のとおりです。

nanosleep({1, 0}, {0, 294396497})       = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
restart_syscall(<... resuming interrupted call ...>) = 0

したがって、信号が受信されたが無視されたことを示しています。質問に完全に答えるには、プロセスがシグナルを無視リストに追加した理由を理解する必要がありますが、強制的にそれらのブロックを解除すると、次のようにpcntl_sigprocmask()なります。

pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGCONT]);

その後、すべてがうまくいき、再生成されたプロセスは意図したとおりにシグナルを受信/処理します。SIGCONTたとえば、ブロック解除のためだけに追加しようとしましたが、正しく処理されましSIGTERMたが、ブロックされていました。

解決策: なんらかの理由で、シグナル ハンドラがインストールされた状態でプロセスが生成されると、新しいインスタンスはそれらのシグナルを無視するようにマスクされます。それらをマスク解除すると問題は強制的に解決されますが、なぜ新しいインスタンスでシグナルがマスクされるのか - それは今のところ未解決の問題です。

于 2015-03-16T12:33:28.977 に答える
0

これは、system(foo) を実行して子プロセスを生成し、現在のプロセスの終了に進むためです。したがって、プロセスは孤立し、その親は PID 1 (init) になります。

コマンドを使用して変更を確認できますpstree

前:

init─┬─cron
(...)
     └─screen─┬─zsh───pstree
              ├─3*[zsh]
              ├─zsh───php
              └─zsh───vim

後:

init─┬─cron
(...)
     └─php

ウィキペディアの状態:

孤立プロセスは、ゾンビ プロセスの反対の状況のようなものです。親プロセスが子プロセスの前に終了する場合を指すためです。

子プロセスが (SIGCHLD シグナルを介して) 終了したときに発生する非同期の子から親への通知とは異なり、子プロセスは親が終了してもすぐには通知されません。代わりに、システムは子プロセスのデータの「parent-pid」フィールドを、システム内の他のすべてのプロセスの「祖先」であるプロセスとして再定義するだけで、そのプロセスの pid は通常 1 (1) の値を持ち、その名前は伝統的に「init」です。したがって、「init はシステム上のすべての孤立したプロセスを「採用」する」と言われています。

あなたの状況では、次の 2 つのオプションをお勧めします。

  • 2 つのスクリプトを使用します。
  • または、両方を含む 1 つのスクリプトを使用します。外側の部分が管理し、外側からフォークされた内側の部分がジョブを実行します。
于 2015-03-16T10:13:30.357 に答える