0

symfony コンポーネント Process ( http://symfony.com/doc/current/components/process.html )を使用して、一連のバックグラウンド プロセスを管理する小さなスクリプトを作成しようとしています。

これが正しく機能するために、メインプロセス、主に SIGINT ( ctrl + c) に送信されるシグナルを処理したいと考えています。

メイン プロセスがこのシグナルを受け取ると、新しいプロセスの開始を停止し、現在のすべてのプロセスが終了するのを待ってから終了する必要があります。

メインプロセスでシグナルを正常にキャッチしましたが、問題は、子プロセスもシグナルを取得してすぐに終了することです。

この動作を変更したり、この信号を中断したりする方法はありますか?

これは、動作を示すためのサンプル スクリプトです。

#!/usr/bin/env php
<?php
require_once __DIR__ . "/vendor/autoload.php";
use Symfony\Component\Process\Process;

$process = new Process("sleep 10");
$process->start();

$exitHandler = function ($signo) use ($process) {
    print "Got signal {$signo}\n";
    while ($process->isRunning()) {
        usleep(10000);
    }
    exit;
};
pcntl_signal(SIGINT, $exitHandler);

while (true) {
    pcntl_signal_dispatch();
    sleep(1);
}

このスクリプトを実行し、シグナルを送信すると (ctrl + c を押す)、親プロセスと子プロセスがすぐに停止します)。

while ループを isRunning 呼び出しに置き換え、スリープをプロセスの wait-method の呼び出しに置き換えると、RuntimeException が発生します。

より手動のアプローチを取り、exec でビルドされた phps を使用して子プロセスを実行すると、必要な動作が得られます。

#!/usr/bin/env php

<?php
require_once __DIR__ . "/vendor/autoload.php";

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", "sleep 10", "/dev/null", "/tmp/testscript.pid"));

$exitHandler = function ($signo) {
    print "Got signal {$signo}\n";
    $pid = file_get_contents("/tmp/testscript.pid");
    while (isRunning($pid)) {
        usleep(10000);
    }
    exit;
};
pcntl_signal(SIGINT, $exitHandler);

while (true) {
    pcntl_signal_dispatch();
    sleep(1);
}

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}

この場合、シグナルを送信すると、メイン プロセスはその子プロセスが終了するまで待機します。

symfony プロセス コンポーネントで動作を取得する方法はありますか?

4

1 に答える 1

2

Symfony の Process の動作ではなく、UNIX 端末での ctrl+c の動作です。端末で ctrl+cを押すと、プロセス グループ(親プロセスとすべての子プロセス)にシグナルが送信されます。

sleep子プロセスではないため、手動のアプローチが機能します。Symfony のコンポーネントを使用したい場合は、子のプロセス グループposix_setpgid次のように変更できます。

$otherGroup = posix_getpgid(posix_getppid());
posix_setpgid($process->getPid(), $otherGroup);

その後、シグナルは に送信されません$process。これは、最近同様の問題に取り組んだときに見つけた唯一の有効な解決策です。

リサーチ

プロセス グループへのシグナルの送信

子プロセスは Symfony の例で作成されます。ターミナルで確認できます。

# find pid of your script
ps -aux | grep "myscript.php"

# show process tree
pstree -a pid
# you will see that sleep is child process
php myscript.php
  └─sh -c sleep 20
      └─sleep 20

プロセスに関する情報を次のように出力すると、プロセス グループに送信されたシグナルが適切に表示されます$exitHandler

$exitHandler = function ($signo) use ($process) {
    print "Got signal {$signo}\n";
    while ($process->isRunning()) {
        usleep(10000);
    }
    $isSignaled = $process->hasBeenSignaled() ? 'YES' : 'NO';
    echo "Signaled? {$isSignaled}\n";
    echo "Exit code: {$process->getExitCode()}\n\n";
    exit;
};

ctrl+c を押すか、プロセス グループを強制終了すると:

# kill process group (like in ctrl+c)
kill -SIGINT -pid

# $exitHandler's output
Got signal 2
Signaled? YES
Exit code: 130

シグナルが親プロセスにのみ送信される場合、期待される動作が得られます。

# kill only main process
kill -SIGINT pid

# $exitHandler's output
Got signal 2
Signaled? NO
Exit code: 0

これで解決策は明らかです。子プロセスを作成したり、プロセス グループを変更したりしないでください。そのため、シグナルは親プロセスにのみ送信されます。

プロセスグループ変更のデメリット

実際の子プロセスが使用されていない場合の結果に注意してください。$process親プロセスが で強制終了されても終了しませんSIGKILL$process実行時間の長いスクリプトの場合、親プロセスの再起動後に複数のインスタンスが実行される可能性があります。を開始する前に、実行中のプロセスを確認することをお勧めし$processます。

于 2016-02-05T17:12:09.663 に答える