[Process] Add signal and getPid methods

This commit is contained in:
Romain Neutron 2012-09-09 16:56:43 +02:00 committed by Fabien Potencier
parent 6f0a5ad314
commit 5ed2737d54
6 changed files with 281 additions and 8 deletions

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;
/**
@ -467,6 +468,51 @@ class Process
return $this->exitcode;
}
/**
* Returns the Pid (process identifier), if applicable.
*
* @return integer|null The process id if running, null otherwise
*
* @throws RuntimeException In case --enable-sigchild is activated
*/
public function getPid()
{
if ($this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
}
$this->updateStatus();
return $this->isRunning() ? $this->processInformation['pid'] : null;
}
/**
* Sends a posix signal to the process.
*
* @param integer $signal A valid posix signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @return Process
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated
* @throws RuntimeException In case of failure
*/
public function signal($signal)
{
if (!$this->isRunning()) {
throw new LogicException('Can not send signal on a non running process.');
}
if ($this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
}
if (true !== @proc_terminate($this->process, $signal)) {
throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal));
}
return $this;
}
/**
* Returns the current output of the process (STDOUT).
*
@ -714,12 +760,13 @@ class Process
* Stops the process.
*
* @param integer|float $timeout The timeout in seconds
* @param integer $signal A posix signal to send in case the process has not stop at timeout, default is SIGKILL
*
* @return integer The exit-code of the process
*
* @throws RuntimeException if the process got signaled
*/
public function stop($timeout = 10)
public function stop($timeout = 10, $signal = null)
{
$timeoutMicro = (int) $timeout*1E6;
if ($this->isRunning()) {
@ -730,8 +777,10 @@ class Process
usleep(1000);
}
if (!defined('PHP_WINDOWS_VERSION_BUILD') && $this->isRunning()) {
proc_terminate($this->process, SIGKILL);
if ($this->isRunning() && !$this->isSigchildEnabled()) {
if (null !== $signal || defined('SIGKILL')) {
$this->signal($signal ?: SIGKILL);
}
}
foreach ($this->pipes as $pipe) {

View File

@ -50,9 +50,6 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->markTestSkipped('Stop with timeout does not work on windows, it requires posix signals');
}
if (!function_exists('pcntl_signal')) {
$this->markTestSkipped('This test require pcntl_signal function');
}
// exec is mandatory here since we send a signal to the process
// see https://github.com/symfony/symfony/issues/5030 about prepending
@ -61,7 +58,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$p->start();
usleep(100000);
$start = microtime(true);
$p->stop(1.1);
$p->stop(1.1, SIGKILL);
while ($p->isRunning()) {
usleep(1000);
}
@ -224,7 +221,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testStatus()
{
$process = $this->getProcess('php -r "sleep(1);"');
$process = $this->getProcess('php -r "usleep(500000);"');
$this->assertFalse($process->isRunning());
$this->assertFalse($process->isStarted());
$this->assertFalse($process->isTerminated());
@ -277,6 +274,17 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($process->hasBeenSignaled());
}
public function testProcessWithoutTermSignalIsNotSignaled()
{
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->markTestSkipped('Windows does not support POSIX signals');
}
$process = $this->getProcess('php -m');
$process->run();
$this->assertFalse($process->hasBeenSignaled());
}
public function testProcessWithoutTermSignal()
{
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
@ -387,6 +395,70 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->assertLessThan($timeout + $precision, $duration);
}
public function testGetPid()
{
$process = $this->getProcess('php -r "sleep(1);"');
$process->start();
$this->assertGreaterThan(0, $process->getPid());
$process->stop();
}
public function testGetPidIsNullBeforeStart()
{
$process = $this->getProcess('php -r "sleep(1);"');
$this->assertNull($process->getPid());
}
public function testGetPidIsNullAfterRun()
{
$process = $this->getProcess('php -m');
$process->run();
$this->assertNull($process->getPid());
}
public function testSignal()
{
$process = $this->getProcess('exec php -f ' . __DIR__ . '/SignalListener.php');
$process->start();
usleep(500000);
$process->signal(SIGUSR1);
while ($process->isRunning() && false === strpos($process->getoutput(), 'Caught SIGUSR1')) {
usleep(10000);
}
$this->assertEquals('Caught SIGUSR1', $process->getOutput());
}
/**
* @expectedException Symfony\Component\Process\Exception\LogicException
*/
public function testSignalProcessNotRunning()
{
$process = $this->getProcess('php -m');
$process->signal(SIGHUP);
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignalWithWrongIntSignal()
{
$process = $this->getProcess('php -r "sleep(3);"');
$process->start();
$process->signal(-4);
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignalWithWrongNonIntSignal()
{
$process = $this->getProcess('php -r "sleep(3);"');
$process->start();
$process->signal('Céphalopodes');
}
public function responsesCodeProvider()
{
return array(

View File

@ -64,6 +64,30 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
*/
public function testGetPid()
{
parent::testGetPid();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testGetPidIsNullBeforeStart()
{
parent::testGetPidIsNullBeforeStart();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testGetPidIsNullAfterRun()
{
parent::testGetPidIsNullAfterRun();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testExitCodeText()
{
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
@ -88,6 +112,27 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
parent::testIsNotSuccessful();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignal()
{
parent::testSignal();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testProcessWithoutTermSignalIsNotSignaled()
{
parent::testProcessWithoutTermSignalIsNotSignaled();
}
public function testStopWithTimeoutIsActuallyWorking()
{
$this->markTestSkipped('Stopping with signal is not supported in sigchild environment');
}
/**
* {@inheritdoc}
*/

View File

@ -45,6 +45,30 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
parent::testProcessWithoutTermSignal();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testGetPid()
{
parent::testGetPid();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testGetPidIsNullBeforeStart()
{
parent::testGetPidIsNullBeforeStart();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testGetPidIsNullAfterRun()
{
parent::testGetPidIsNullAfterRun();
}
public function testExitCodeText()
{
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
@ -53,6 +77,22 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
$this->assertInternalType('string', $process->getExitCodeText());
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignal()
{
parent::testSignal();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testProcessWithoutTermSignalIsNotSignaled()
{
parent::testProcessWithoutTermSignalIsNotSignaled();
}
/**
* {@inheritdoc}
*/

View File

@ -0,0 +1,16 @@
<?php
// required for signal handling
declare(ticks = 1);
pcntl_signal(SIGUSR1, function(){echo "Caught SIGUSR1"; exit;});
$n=0;
// ticks require activity to work - sleep(4); does not work
while($n < 400) {
usleep(10000);
$n++;
}
return;

View File

@ -79,6 +79,57 @@ class SimpleProcessTest extends AbstractProcessTest
parent::testIsNotSuccessful();
}
public function testGetPid()
{
$this->skipIfPHPSigchild();
parent::testGetPid();
}
public function testGetPidIsNullBeforeStart()
{
$this->skipIfPHPSigchild();
parent::testGetPidIsNullBeforeStart();
}
public function testGetPidIsNullAfterRun()
{
$this->skipIfPHPSigchild();
parent::testGetPidIsNullAfterRun();
}
public function testSignal()
{
$this->skipIfPHPSigchild();
parent::testSignal();
}
/**
* @expectedException Symfony\Component\Process\Exception\LogicException
*/
public function testSignalProcessNotRunning()
{
$this->skipIfPHPSigchild();
parent::testSignalProcessNotRunning();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignalWithWrongIntSignal()
{
$this->skipIfPHPSigchild();
parent::testSignalWithWrongIntSignal();
}
/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignalWithWrongNonIntSignal()
{
$this->skipIfPHPSigchild();
parent::testSignalWithWrongNonIntSignal();
}
/**
* {@inheritdoc}
*/