[Process] Fix stopping a process on Windows
This commit is contained in:
parent
d9b8d0c1e9
commit
80fb51c3af
@ -157,8 +157,16 @@ class Process
|
|||||||
|
|
||||||
public function __destruct()
|
public function __destruct()
|
||||||
{
|
{
|
||||||
// stop() will check if we have a process running.
|
if ($this->isRunning()) {
|
||||||
$this->stop();
|
$this->doSignal(15, false);
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
if ($this->isRunning()) {
|
||||||
|
usleep(100000);
|
||||||
|
$this->doSignal(9, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't call ->stop() nor ->close() since we don't want to wait for the subprocess here
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __clone()
|
public function __clone()
|
||||||
@ -1190,7 +1198,7 @@ class Process
|
|||||||
|
|
||||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||||
exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode);
|
exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode);
|
||||||
if ($exitCode) {
|
if ($exitCode && $this->isRunning()) {
|
||||||
if ($throwException) {
|
if ($throwException) {
|
||||||
throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
|
throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,10 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
|
$this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Process\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage STDIN can not be set while the process is running.
|
||||||
|
*/
|
||||||
public function testSetStdinWhileRunningThrowsAnException()
|
public function testSetStdinWhileRunningThrowsAnException()
|
||||||
{
|
{
|
||||||
$process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
|
$process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
|
||||||
@ -176,9 +180,10 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$process->stop();
|
$process->stop();
|
||||||
$this->fail('A LogicException should have been raised.');
|
$this->fail('A LogicException should have been raised.');
|
||||||
} catch (LogicException $e) {
|
} catch (LogicException $e) {
|
||||||
$this->assertEquals('STDIN can not be set while the process is running.', $e->getMessage());
|
|
||||||
}
|
}
|
||||||
$process->stop();
|
$process->stop();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -659,6 +664,10 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertNotEquals($process1->getOutput(), $process2->getOutput());
|
$this->assertNotEquals($process1->getOutput(), $process2->getOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Process\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage The process timed-out.
|
||||||
|
*/
|
||||||
public function testRunProcessWithTimeout()
|
public function testRunProcessWithTimeout()
|
||||||
{
|
{
|
||||||
$timeout = 0.5;
|
$timeout = 0.5;
|
||||||
@ -672,14 +681,13 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
}
|
}
|
||||||
$duration = microtime(true) - $start;
|
$duration = microtime(true) - $start;
|
||||||
|
|
||||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
if ('\\' !== DIRECTORY_SEPARATOR) {
|
||||||
// Windows is a bit slower as it read file handles, then allow twice the precision
|
// On Windows, timers are too transient
|
||||||
$maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION;
|
|
||||||
} else {
|
|
||||||
$maxDuration = $timeout + Process::TIMEOUT_PRECISION;
|
$maxDuration = $timeout + Process::TIMEOUT_PRECISION;
|
||||||
|
$this->assertLessThan($maxDuration, $duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertLessThan($maxDuration, $duration);
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCheckTimeoutOnNonStartedProcess()
|
public function testCheckTimeoutOnNonStartedProcess()
|
||||||
@ -695,6 +703,10 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$process->checkTimeout();
|
$process->checkTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Process\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage The process timed-out.
|
||||||
|
*/
|
||||||
public function testCheckTimeoutOnStartedProcess()
|
public function testCheckTimeoutOnStartedProcess()
|
||||||
{
|
{
|
||||||
$timeout = 0.5;
|
$timeout = 0.5;
|
||||||
@ -717,8 +729,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$this->assertLessThan($timeout + $precision, $duration);
|
$this->assertLessThan($timeout + $precision, $duration);
|
||||||
$this->assertFalse($process->isSuccessful());
|
$this->assertFalse($process->isSuccessful());
|
||||||
|
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Process\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage The process timed-out.
|
||||||
|
*/
|
||||||
public function testStartAfterATimeout()
|
public function testStartAfterATimeout()
|
||||||
{
|
{
|
||||||
$process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 1000; while ($n--) {echo \'\'; usleep(1000); }')));
|
$process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 1000; while ($n--) {echo \'\'; usleep(1000); }')));
|
||||||
@ -731,6 +749,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$process->start();
|
$process->start();
|
||||||
usleep(10000);
|
usleep(10000);
|
||||||
$process->stop();
|
$process->stop();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetPid()
|
public function testGetPid()
|
||||||
@ -760,14 +780,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->markTestSkipped('Extension pcntl is required.');
|
$this->markTestSkipped('Extension pcntl is required.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$process = $this->getProcess('exec php -f '.__DIR__.'/SignalListener.php');
|
$process = $this->getProcess('exec '.self::$phpBin.' '.__DIR__.'/SignalListener.php');
|
||||||
$process->start();
|
$process->start();
|
||||||
usleep(500000);
|
|
||||||
$process->signal(SIGUSR1);
|
|
||||||
|
|
||||||
while ($process->isRunning() && false === strpos($process->getOutput(), 'Caught SIGUSR1')) {
|
while (false === strpos($process->getOutput(), 'Caught')) {
|
||||||
usleep(10000);
|
usleep(1000);
|
||||||
}
|
}
|
||||||
|
$process->signal(SIGUSR1);
|
||||||
|
$process->wait();
|
||||||
|
|
||||||
$this->assertEquals('Caught SIGUSR1', $process->getOutput());
|
$this->assertEquals('Caught SIGUSR1', $process->getOutput());
|
||||||
}
|
}
|
||||||
@ -828,6 +848,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideMethodsThatNeedATerminatedProcess
|
* @dataProvider provideMethodsThatNeedATerminatedProcess
|
||||||
|
* @expectedException Symfony\Component\Process\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage Process must be terminated before calling
|
||||||
*/
|
*/
|
||||||
public function testMethodsThatNeedATerminatedProcess($method)
|
public function testMethodsThatNeedATerminatedProcess($method)
|
||||||
{
|
{
|
||||||
@ -838,10 +860,10 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$process->stop(0);
|
$process->stop(0);
|
||||||
$this->fail('A LogicException must have been thrown');
|
$this->fail('A LogicException must have been thrown');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->assertInstanceOf('Symfony\Component\Process\Exception\LogicException', $e);
|
|
||||||
$this->assertEquals(sprintf('Process must be terminated before calling %s.', $method), $e->getMessage());
|
|
||||||
}
|
}
|
||||||
$process->stop(0);
|
$process->stop(0);
|
||||||
|
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideMethodsThatNeedATerminatedProcess()
|
public function provideMethodsThatNeedATerminatedProcess()
|
||||||
|
@ -112,6 +112,10 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
|
|||||||
$this->markTestSkipped('Signal is not supported in sigchild environment');
|
$this->markTestSkipped('Signal is not supported in sigchild environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Process\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage The process timed-out.
|
||||||
|
*/
|
||||||
public function testStartAfterATimeout()
|
public function testStartAfterATimeout()
|
||||||
{
|
{
|
||||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||||
|
@ -9,17 +9,13 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// required for signal handling
|
pcntl_signal(SIGUSR1, function () {echo 'SIGUSR1'; exit;});
|
||||||
declare (ticks = 1);
|
|
||||||
|
|
||||||
pcntl_signal(SIGUSR1, function () {echo 'Caught SIGUSR1'; exit;});
|
echo 'Caught ';
|
||||||
|
|
||||||
$n = 0;
|
$n = 0;
|
||||||
|
|
||||||
// ticks require activity to work - sleep(4); does not work
|
while ($n++ < 400) {
|
||||||
while ($n < 400) {
|
|
||||||
usleep(10000);
|
usleep(10000);
|
||||||
++$n;
|
pcntl_signal_dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
|
Reference in New Issue
Block a user