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
.
之前检查 运行 进程是个好主意
我正在尝试使用 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
.