信号被转发给儿童以获得symfony进程组件

时间:2016-01-21 14:36:02

标签: symfony command-line process signals symfony-process

我正在尝试编写一个小脚本,使用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进程组件中获取行为?

1 个答案:

答案 0 :(得分:1)

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

手动方法有效,因为sleep不是子进程。如果您想使用Symfony的组件,change child's process groupposix_setpgid

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

然后信号不会被发送到$process。这是我最近解决类似问题时发现的唯一可行解决方案。

研究

向进程组发送信号

在Symfony示例中创建了

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之前检查正在运行的进程是个好主意。