Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Process] Add signal and getPid methods #5476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions src/Symfony/Component/Process/Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -467,6 +468,51 @@ public function wait($callback = null)
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).
*
Expand Down Expand Up @@ -714,12 +760,13 @@ public function getStatus()
* 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about using SIGKILL as the default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned above, I was wondering if SIGKILL is defined on windows. I just had confirmation that it SIGKILL is not defined on windows environment.

We could define a Process constant :

class Process
{
    const SIGKILL = 9;

    public function stop($timeout = 10, $signal = self::SIGKILL)
    {
        // ...
    }
}

Another workaround is to define SIGKILL is the environment :

if (!defined('SIGKILL')) {
    define('SIGKILL', 9);
}

class Process
{
    public function stop($timeout = 10, $signal = SIGKILL)
    {
        // ...
    }
}

But I don't like both of these workaround so much

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... or you could just keep null as the default value and set it to SIGKILL if $signal is null and not on Windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you would not enjoy such behavior :) okay, I like it, I'm gonna do this tonight

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just got a report of an undefined constant SIGKILL on what appears to be an OSX machine :| composer/composer#1791

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, let's check if the constant is defined. thanks for the report !

{
$timeoutMicro = (int) $timeout*1E6;
if ($this->isRunning()) {
Expand All @@ -730,8 +777,10 @@ public function stop($timeout = 10)
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) {
Expand Down
82 changes: 77 additions & 5 deletions src/Symfony/Component/Process/Tests/AbstractProcessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ public function testStopWithTimeoutIsActuallyWorking()
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
Expand All @@ -61,7 +58,7 @@ public function testStopWithTimeoutIsActuallyWorking()
$p->start();
usleep(100000);
$start = microtime(true);
$p->stop(1.1);
$p->stop(1.1, SIGKILL);
while ($p->isRunning()) {
usleep(1000);
}
Expand Down Expand Up @@ -224,7 +221,7 @@ public function testGetExitCode()

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());
Expand Down Expand Up @@ -277,6 +274,17 @@ public function testProcessIsNotSignaled()
$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')) {
Expand Down Expand Up @@ -387,6 +395,70 @@ public function testCheckTimeoutOnStartedProcess()
$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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ public function 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();
}

/**
* @expectedException Symfony\Component\Process\Exception\RuntimeException
*/
public function testExitCodeText()
{
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
Expand All @@ -88,6 +112,27 @@ public function 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}
*/
Expand Down
40 changes: 40 additions & 0 deletions src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,30 @@ public function 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()
{
$process = $this->getProcess('qdfsmfkqsdfmqmsd');
Expand All @@ -53,6 +77,22 @@ public function testExitCodeText()
$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}
*/
Expand Down
16 changes: 16 additions & 0 deletions src/Symfony/Component/Process/Tests/SignalListener.php
Original file line number Diff line number Diff line change
@@ -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;
51 changes: 51 additions & 0 deletions src/Symfony/Component/Process/Tests/SimpleProcessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,57 @@ public function 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}
*/
Expand Down