symfony 进程组件的信号被转发到 children

Signal being forwarded to children for the symfony process component

我正在尝试使用 symfony 组件 Process (http://symfony.com/doc/current/components/process.html) 编写一个小脚本来管理一系列后台进程。

为了使其正常工作,我想处理发送到主进程的信号,主要是 SIGINT (ctrl + c)。

当主进程收到这个信号时,应该停止启动新进程,等待所有当前进程退出,然后自己退出。

我在主进程中成功捕捉到信号,但问题是 child-processes 也接收到信号并立即退出。

有什么方法可以改变这种行为或中断这种信号吗?

这是我演示行为的示例脚本。

#!/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)将立即停止 parent 和 child 进程。

如果我将 while-loop 替换为 is运行 调用并将睡眠替换为对进程上 wait-method 的调用,我会收到 RuntimeException 消息:进程已发出信号信号为“2”。

如果我采取更手动的方法并在 exec 中使用 phps build 执行 child 进程,我会得到我想要的行为。

#!/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;
}

在这种情况下,当我发送信号时,主进程会等待 child 完成后再退出。

有什么方法可以获取 symfony 进程组件中的行为吗?

不是Symfony进程的行为,而是UNIX终端ctrl+c的行为。当您 信号 被发送到进程组时 (父进程和所有子进程)。

手动方法有效,因为 sleep 不是子进程。当你想使用 Symfony 的组件时,你可以 change child's process group with posix_setpgid:

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

那么信号就不会发送到$process。这是我最近处理类似问题时找到的唯一可行的解​​决方案。

研究

向进程组发送信号

Child 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:

# 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

现在解决方案很明显了。不要创建子进程或更改进程组,因此信号仅发送给父进程。

更改进程组的缺点

注意未使用 真实 子进程时的后果。当使用 SIGKILL 终止父进程时,$process 不会终止。如果 $process 是 long-运行 脚本,那么您可以在重新启动父进程后拥有多个 运行 实例。在开始 $process.

之前检查 运行 进程是个好主意