[Process] Add signal and getPid methods
This commit is contained in:
parent
6f0a5ad314
commit
5ed2737d54
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\Process;
|
namespace Symfony\Component\Process;
|
||||||
|
|
||||||
use Symfony\Component\Process\Exception\InvalidArgumentException;
|
use Symfony\Component\Process\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Process\Exception\LogicException;
|
||||||
use Symfony\Component\Process\Exception\RuntimeException;
|
use Symfony\Component\Process\Exception\RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -467,6 +468,51 @@ class Process
|
|||||||
return $this->exitcode;
|
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).
|
* Returns the current output of the process (STDOUT).
|
||||||
*
|
*
|
||||||
@ -714,12 +760,13 @@ class Process
|
|||||||
* Stops the process.
|
* Stops the process.
|
||||||
*
|
*
|
||||||
* @param integer|float $timeout The timeout in seconds
|
* @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
|
* @return integer The exit-code of the process
|
||||||
*
|
*
|
||||||
* @throws RuntimeException if the process got signaled
|
* @throws RuntimeException if the process got signaled
|
||||||
*/
|
*/
|
||||||
public function stop($timeout = 10)
|
public function stop($timeout = 10, $signal = null)
|
||||||
{
|
{
|
||||||
$timeoutMicro = (int) $timeout*1E6;
|
$timeoutMicro = (int) $timeout*1E6;
|
||||||
if ($this->isRunning()) {
|
if ($this->isRunning()) {
|
||||||
@ -730,8 +777,10 @@ class Process
|
|||||||
usleep(1000);
|
usleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD') && $this->isRunning()) {
|
if ($this->isRunning() && !$this->isSigchildEnabled()) {
|
||||||
proc_terminate($this->process, SIGKILL);
|
if (null !== $signal || defined('SIGKILL')) {
|
||||||
|
$this->signal($signal ?: SIGKILL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->pipes as $pipe) {
|
foreach ($this->pipes as $pipe) {
|
||||||
|
@ -50,9 +50,6 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||||
$this->markTestSkipped('Stop with timeout does not work on windows, it requires posix signals');
|
$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
|
// exec is mandatory here since we send a signal to the process
|
||||||
// see https://github.com/symfony/symfony/issues/5030 about prepending
|
// see https://github.com/symfony/symfony/issues/5030 about prepending
|
||||||
@ -61,7 +58,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$p->start();
|
$p->start();
|
||||||
usleep(100000);
|
usleep(100000);
|
||||||
$start = microtime(true);
|
$start = microtime(true);
|
||||||
$p->stop(1.1);
|
$p->stop(1.1, SIGKILL);
|
||||||
while ($p->isRunning()) {
|
while ($p->isRunning()) {
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
}
|
}
|
||||||
@ -224,7 +221,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testStatus()
|
public function testStatus()
|
||||||
{
|
{
|
||||||
$process = $this->getProcess('php -r "sleep(1);"');
|
$process = $this->getProcess('php -r "usleep(500000);"');
|
||||||
$this->assertFalse($process->isRunning());
|
$this->assertFalse($process->isRunning());
|
||||||
$this->assertFalse($process->isStarted());
|
$this->assertFalse($process->isStarted());
|
||||||
$this->assertFalse($process->isTerminated());
|
$this->assertFalse($process->isTerminated());
|
||||||
@ -277,6 +274,17 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertFalse($process->hasBeenSignaled());
|
$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()
|
public function testProcessWithoutTermSignal()
|
||||||
{
|
{
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||||
@ -387,6 +395,70 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertLessThan($timeout + $precision, $duration);
|
$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()
|
public function responsesCodeProvider()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
|
@ -64,6 +64,30 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
|
|||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
|
* @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()
|
public function testExitCodeText()
|
||||||
{
|
{
|
||||||
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
|
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
|
||||||
@ -88,6 +112,27 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
|
|||||||
parent::testIsNotSuccessful();
|
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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -45,6 +45,30 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
|
|||||||
parent::testProcessWithoutTermSignal();
|
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()
|
public function testExitCodeText()
|
||||||
{
|
{
|
||||||
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
|
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
|
||||||
@ -53,6 +77,22 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
|
|||||||
$this->assertInternalType('string', $process->getExitCodeText());
|
$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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
16
src/Symfony/Component/Process/Tests/SignalListener.php
Normal file
16
src/Symfony/Component/Process/Tests/SignalListener.php
Normal 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;
|
@ -79,6 +79,57 @@ class SimpleProcessTest extends AbstractProcessTest
|
|||||||
parent::testIsNotSuccessful();
|
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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user