我正在尝试编写一个小脚本,使用symfony组件Process(http://symfony.com/doc/current/components/process.html)来管理一系列后台进程。
为了使其正常工作,我想处理发送到主进程的信号,主要是SIGINT( ctrl + c )。
当主进程收到此信号时,它应该停止启动新进程,等待所有当前进程退出然后退出。
我在主进程中成功捕获了信号,但问题是子进程也获取了信号并立即退出。
有没有办法改变这种行为或中断这个信号?
这是我演示行为的示例脚本。
class XXXXType extends AbstractType {
private $manager;
public function __construct(\Doctrine\Common\Persistence\ObjectManager $manager) {
$this->manager = $manager;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nom', 'text', array('label' => 'Nom *',
'attr' => array('placeholder' => 'Nom ',
'maxlength' => 250,
'trim' => true)
)
)
->add('prenoms', 'text', array('label' => 'Prénom(s) *',
'attr' => array('placeholder' => 'Prénom(s)',
'maxlength' => 250,
'trim' => true)
)
)
->add('date_de_naiss', 'date', array('widget' => 'single_text', 'format' => 'dd-MM-yyyy',
'label' => 'Date de naissance ',
'required' => false, 'data' => null,
'attr' => array('name' => 'datepicker')
)
)
->add('telephone', 'text', array('label' => 'Telephone *',
'attr' => array('placeholder' => '123-456-7890 or 1234567890')
)
)
->add('email', 'email', array('attr' => array('placeholder' => 'xxxx@xxxx.com',),
'trim' => true,
'required' => false,
'data' => null
)
);
$builder->addEventSubscriber(new AddFieldSubscriber($manager));
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'XXXXXXBundle\Entity\Parents'
));
}
/**
* @return string
*/
public function getName() {
return 'XXXXXXXXXsbundle_parents';
}
}
运行此脚本并发送信号(按ctrl + c)将立即停止父进程和子进程。
如果我用isRunning调用替换while循环,并且在进程上调用wait-method就睡眠我得到一个RuntimeException:这个进程已经用信号“2”发出信号。
如果我采用更加手动的方法并使用exec中的phps构建执行子进程,我会得到我想要的行为。
#!/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);
}
在这种情况下,当我发送信号时,主进程会在退出之前等待它的孩子完成。
有没有办法在symfony进程组件中获取行为?
答案 0 :(得分:1)
这不是Symfony进程的行为,而是UNIX终端中ctrl + c的行为。当press ctrl+c in terminal信号被发送到流程组(父流程和所有子流程)时。
手动方法有效,因为sleep
不是子进程。如果您想使用Symfony的组件,change child's process group可posix_setpgid
:
$otherGroup = posix_getpgid(posix_getppid());
posix_setpgid($process->getPid(), $otherGroup);
然后信号不会被发送到$process
。这是我最近解决类似问题时发现的唯一可行解决方案。
Child process。你可以在终端查看。
# 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
现在解决方案很明显。不要创建子进程或更改进程组,因此信号仅发送给父进程。
请注意实际子进程未被使用时的后果。当父进程被$process
终止时,SIGKILL
不会被终止。如果$process
是长时间运行的脚本,则在重新启动父进程后可能会有多个正在运行的实例。在开始$process
之前检查正在运行的进程是个好主意。