From a96a02a2158204b33812c06d3853dd86f9bbc3e0 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Sun, 9 Sep 2012 16:56:43 +0200 Subject: [PATCH 1/3] [Process] Add Process::getPid() method --- src/Symfony/Component/Process/Process.php | 21 ++++++++++++++++ .../Process/Tests/AbstractProcessTest.php | 21 ++++++++++++++++ .../Tests/SigchildDisabledProcessTest.php | 24 +++++++++++++++++++ .../Tests/SigchildEnabledProcessTest.php | 24 +++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 0cfd371f6ed36..f51889510ce24 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -467,6 +467,27 @@ 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; + } + /** * Returns the current output of the process (STDOUT). * diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index ec63a3985603f..0a0c59fcbd118 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -387,6 +387,27 @@ 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 responsesCodeProvider() { return array( diff --git a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php index 1e4dc1d0a2f68..bf2fa1d3739d8 100644 --- a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php @@ -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'); diff --git a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php index 5c664e24233f0..e0b73f19a80f8 100644 --- a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php @@ -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'); From f453af60be622bb66a095c83a95b5086ed925643 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Sun, 9 Sep 2012 23:39:07 +0200 Subject: [PATCH 2/3] [Process] Add Process::signal() method --- src/Symfony/Component/Process/Process.php | 42 +++++++++++--- .../Process/Tests/AbstractProcessTest.php | 58 ++++++++++++++++++- .../Tests/SigchildDisabledProcessTest.php | 24 ++++++++ .../Tests/SigchildEnabledProcessTest.php | 16 +++++ .../Process/Tests/SignalListener.php | 16 +++++ .../Process/Tests/SimpleProcessTest.php | 51 ++++++++++++++++ 6 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 src/Symfony/Component/Process/Tests/SignalListener.php diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index f51889510ce24..3d3c95667c9c7 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -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; /** @@ -477,10 +478,7 @@ public function wait($callback = null) public function getPid() { if ($this->isSigchildEnabled()) { - throw new RuntimeException( - 'This PHP has been compiled with --enable-sigchild.' - . ' The process identifier can not be retrieved.' - ); + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); } $this->updateStatus(); @@ -488,6 +486,33 @@ public function getPid() 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). * @@ -735,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) { $timeoutMicro = (int) $timeout*1E6; if ($this->isRunning()) { @@ -751,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) { diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index 0a0c59fcbd118..e6fb6c96d60b1 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -61,7 +61,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); } @@ -224,7 +224,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()); @@ -277,6 +277,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')) { @@ -408,6 +419,49 @@ public function testGetPidIsNullAfterRun() $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( diff --git a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php index bf2fa1d3739d8..a6d98107dc93b 100644 --- a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php @@ -112,6 +112,30 @@ 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(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testStopWithTimeoutIsActuallyWorking() + { + parent::testStopWithTimeoutIsActuallyWorking(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php index e0b73f19a80f8..deec3a6504311 100644 --- a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php @@ -77,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} */ diff --git a/src/Symfony/Component/Process/Tests/SignalListener.php b/src/Symfony/Component/Process/Tests/SignalListener.php new file mode 100644 index 0000000000000..8ba167c3317b4 --- /dev/null +++ b/src/Symfony/Component/Process/Tests/SignalListener.php @@ -0,0 +1,16 @@ +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} */ From e06c2a700afdbd77eb11fc5784095a14bd0946e2 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 11 Apr 2013 01:34:27 +0200 Subject: [PATCH 3/3] Fix unit tests --- src/Symfony/Component/Process/Tests/AbstractProcessTest.php | 3 --- .../Component/Process/Tests/SigchildDisabledProcessTest.php | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index e6fb6c96d60b1..3d1bcf37e4db7 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -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 diff --git a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php index a6d98107dc93b..37c348d35dba9 100644 --- a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php @@ -128,12 +128,9 @@ public function testProcessWithoutTermSignalIsNotSignaled() parent::testProcessWithoutTermSignalIsNotSignaled(); } - /** - * @expectedException Symfony\Component\Process\Exception\RuntimeException - */ public function testStopWithTimeoutIsActuallyWorking() { - parent::testStopWithTimeoutIsActuallyWorking(); + $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); } /**