diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 2ccad53d77..3185633fbf 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -64,6 +64,8 @@ class Process /** @var ProcessPipes */ private $processPipes; + private $latestSignal; + private static $sigchild; /** @@ -321,7 +323,7 @@ class Process usleep(1000); } - if ($this->processInformation['signaled']) { + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); } @@ -661,7 +663,8 @@ class Process throw new RuntimeException('Unable to kill the process'); } } - proc_terminate($this->process); + // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); do { usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); @@ -1158,6 +1161,7 @@ class Process $this->stdout = null; $this->stderr = null; $this->process = null; + $this->latestSignal = null; $this->status = self::STATUS_READY; $this->incrementalOutputOffset = 0; $this->incrementalErrorOutputOffset = 0; @@ -1201,6 +1205,8 @@ class Process return false; } + $this->latestSignal = $signal; + return true; } diff --git a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php index 69ad3d5b09..cb06f28968 100644 --- a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php @@ -147,6 +147,50 @@ class SimpleProcessTest extends AbstractProcessTest parent::testSignalWithWrongNonIntSignal(); } + public function testStopTerminatesProcessCleanly() + { + try { + $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + $process->stop(); + }); + } catch (RuntimeException $e) { + $this->fail('A call to stop() is not expected to cause wait() to throw a RuntimeException'); + } + } + + public function testKillSignalTerminatesProcessCleanly() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + + try { + $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(SIGKILL); + } + }); + } catch (RuntimeException $e) { + $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + } + + public function testTermSignalTerminatesProcessCleanly() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + + try { + $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(SIGTERM); + } + }); + } catch (RuntimeException $e) { + $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + } + /** * {@inheritdoc} */