Skip to content

Commit 5d25c8d

Browse files
committed
bug symfony#10455 [2.3][Process] Fix random failures in test suite on TravisCI (romainneutron)
This PR was merged into the 2.3 branch. Discussion ---------- [2.3][Process] Fix random failures in test suite on TravisCI | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | License | MIT Commits ------- 783e377 [Process] Avoid failures because of slow IOs 238565e [Process] Avoid failure because of a slow process 173f8c5 [Process] Avoid failure when calling Process::stop in edge cases
2 parents 123dcac + 783e377 commit 5d25c8d

File tree

3 files changed

+61
-22
lines changed

3 files changed

+61
-22
lines changed

src/Symfony/Component/Process/Process.php

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -356,17 +356,7 @@ public function getPid()
356356
*/
357357
public function signal($signal)
358358
{
359-
if (!$this->isRunning()) {
360-
throw new LogicException('Can not send signal on a non running process.');
361-
}
362-
363-
if ($this->isSigchildEnabled()) {
364-
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
365-
}
366-
367-
if (true !== @proc_terminate($this->process, $signal)) {
368-
throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal));
369-
}
359+
$this->doSignal($signal, true);
370360

371361
return $this;
372362
}
@@ -635,7 +625,11 @@ public function stop($timeout = 10, $signal = null)
635625

636626
if ($this->isRunning() && !$this->isSigchildEnabled()) {
637627
if (null !== $signal || defined('SIGKILL')) {
638-
$this->signal($signal ?: SIGKILL);
628+
// avoid exception here :
629+
// process is supposed to be running, but it might have stop
630+
// just after this line.
631+
// in any case, let's silently discard the error, we can not do anything
632+
$this->doSignal($signal ?: SIGKILL, false);
639633
}
640634
}
641635
}
@@ -1111,4 +1105,44 @@ private function resetProcessData()
11111105
$this->incrementalOutputOffset = 0;
11121106
$this->incrementalErrorOutputOffset = 0;
11131107
}
1108+
1109+
/**
1110+
* Sends a POSIX signal to the process.
1111+
*
1112+
* @param integer $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
1113+
* @param Boolean $throwException True to throw exception in case signal failed, false otherwise
1114+
* @return Boolean True if the signal was sent successfully, false otherwise
1115+
*
1116+
* @throws LogicException In case the process is not running
1117+
* @throws RuntimeException In case --enable-sigchild is activated
1118+
* @throws RuntimeException In case of failure
1119+
*/
1120+
private function doSignal($signal, $throwException)
1121+
{
1122+
if (!$this->isRunning()) {
1123+
if ($throwException) {
1124+
throw new LogicException('Can not send signal on a non running process.');
1125+
}
1126+
1127+
return false;
1128+
}
1129+
1130+
if ($this->isSigchildEnabled()) {
1131+
if ($throwException) {
1132+
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
1133+
}
1134+
1135+
return false;
1136+
}
1137+
1138+
if (true !== @proc_terminate($this->process, $signal)) {
1139+
if ($throwException) {
1140+
throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal));
1141+
}
1142+
1143+
return false;
1144+
}
1145+
1146+
return true;
1147+
}
11141148
}

src/Symfony/Component/Process/ProcessPipes.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class ProcessPipes
2929
/** @var Boolean */
3030
private $ttyMode;
3131

32+
const CHUNK_SIZE = 16384;
33+
3234
public function __construct($useFiles, $ttyMode)
3335
{
3436
$this->useFiles = (Boolean) $useFiles;
@@ -233,7 +235,7 @@ private function readFileHandles($close = false)
233235
$data = '';
234236
$dataread = null;
235237
while (!feof($fileHandle)) {
236-
if (false !== $dataread = fread($fileHandle, 16392)) {
238+
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
237239
$data .= $dataread;
238240
}
239241
}
@@ -291,7 +293,7 @@ private function readStreams($blocking, $close = false)
291293
$type = array_search($pipe, $this->pipes);
292294

293295
$data = '';
294-
while ($dataread = fread($pipe, 8192)) {
296+
while ($dataread = fread($pipe, self::CHUNK_SIZE)) {
295297
$data .= $dataread;
296298
}
297299

src/Symfony/Component/Process/Tests/AbstractProcessTest.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Process\Process;
1515
use Symfony\Component\Process\Exception\RuntimeException;
16+
use Symfony\Component\Process\ProcessPipes;
1617

1718
/**
1819
* @author Robert Schönthal <[email protected]>
@@ -87,18 +88,20 @@ public function testAllOutputIsActuallyReadOnTermination()
8788
// has terminated so the internal pipes array is already empty. normally
8889
// the call to start() will not read any data as the process will not have
8990
// generated output, but this is non-deterministic so we must count it as
90-
// a possibility. therefore we need 2 * 8192 plus another byte which will
91-
// never be read.
92-
$expectedOutputSize = 16385;
91+
// a possibility. therefore we need 2 * ProcessPipes::CHUNK_SIZE plus
92+
// another byte which will never be read.
93+
$expectedOutputSize = ProcessPipes::CHUNK_SIZE * 2 + 2;
9394

9495
$code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
9596
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
9697

9798
$p->start();
98-
usleep(250000);
99+
// Let's wait enough time for process to finish...
100+
// Here we don't call Process::run or Process::wait to avoid any read of pipes
101+
usleep(500000);
99102

100103
if ($p->isRunning()) {
101-
$this->fail('Process execution did not complete in the required time frame');
104+
$this->markTestSkipped('Process execution did not complete in the required time frame');
102105
}
103106

104107
$o = $p->getOutput();
@@ -200,7 +203,7 @@ public function testGetErrorOutput()
200203

201204
public function testGetIncrementalErrorOutput()
202205
{
203-
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { usleep(50000); file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
206+
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { usleep(100000); file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
204207

205208
$p->start();
206209
while ($p->isRunning()) {
@@ -247,7 +250,7 @@ public function testTTYCommand()
247250
$this->markTestSkipped('Windows does have /dev/tty support');
248251
}
249252

250-
$process = $this->getProcess('echo "foo" >> /dev/null');
253+
$process = $this->getProcess('echo "foo" >> /dev/null && php -r "usleep(100000);"');
251254
$process->setTTY(true);
252255
$process->start();
253256
$this->assertTrue($process->isRunning());
@@ -468,7 +471,7 @@ public function testRestart()
468471
$process1->run();
469472
$process2 = $process1->restart();
470473

471-
usleep(300000); // wait for output
474+
$process2->wait(); // wait for output
472475

473476
// Ensure that both processed finished and the output is numeric
474477
$this->assertFalse($process1->isRunning());

0 commit comments

Comments
 (0)