Skip to content

Commit db6239d

Browse files
[Process] Enhance compatiblity with --enable-sigchild
1 parent ed22696 commit db6239d

8 files changed

+184
-706
lines changed

.travis.yml

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ addons:
1010
cache:
1111
directories:
1212
- .phpunit
13+
- php-5.3.9
1314

1415
matrix:
1516
include:
@@ -32,6 +33,7 @@ env:
3233

3334
before_install:
3435
- if [[ "$deps" = "no" ]] && [[ "$TRAVIS_PHP_VERSION" =~ 5.[45] ]] && [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then export deps=skip; fi;
36+
- if [[ $deps = no && $TRAVIS_PHP_VERSION = 5.3 && ! -d php-5.3.9/sapi ]]; then wget http://museum.php.net/php5/php-5.3.9.tar.bz2; tar -xjf php-5.3.9.tar.bz2; (cd php-5.3.9; ./configure --enable-sigchild --enable-pcntl; make -j2); fi;
3537
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi;
3638
- echo "memory_limit = -1" >> $INI_FILE
3739
- echo "session.gc_probability = 0" >> $INI_FILE
@@ -54,6 +56,7 @@ install:
5456
script:
5557
- if [ "$deps" = "no" ]; then echo "$COMPONENTS" | parallel --gnu '$PHPUNIT --exclude-group tty,benchmark,intl-data {}'; fi;
5658
- if [ "$deps" = "no" ]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi;
59+
- if [[ $deps = no && $TRAVIS_PHP_VERSION = 5.3 ]]; then echo -e "1\\n0" | parallel --gnu 'echo -e "\\nPHP --enable-sigchild enhanced={}" && ENHANCE_SIGCHLD={} php-5.3.9/sapi/cli/php .phpunit/phpunit-4.8/phpunit src/Symfony/Component/Process/'; fi;
5760
- if [ "$deps" = "high" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi;
5861
- if [ "$deps" = "low" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source --prefer-lowest --prefer-stable update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi;
5962
- if [ "$deps" = "skip" ]; then echo 'This matrix line is skipped for pull requests.'; fi;

src/Symfony/Component/Process/Process.php

+63-55
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Process
4646
private $timeout;
4747
private $options;
4848
private $exitcode;
49-
private $fallbackExitcode;
49+
private $fallbackStatus = array();
5050
private $processInformation;
5151
private $stdout;
5252
private $stderr;
@@ -65,6 +65,14 @@ class Process
6565
private $latestSignal;
6666

6767
private static $sigchild;
68+
private static $posixSignals = array(
69+
1 => 1, // SIGHUP
70+
2 => 2, // SIGINT
71+
3 => 3, // SIGQUIT
72+
6 => 6, // SIGABRT
73+
14 => 14, // SIGALRM
74+
15 => 15, // SIGTERM
75+
);
6876

6977
/**
7078
* Exit codes translation table.
@@ -339,17 +347,9 @@ public function wait($callback = null)
339347
* Returns the Pid (process identifier), if applicable.
340348
*
341349
* @return int|null The process id if running, null otherwise
342-
*
343-
* @throws RuntimeException In case --enable-sigchild is activated
344350
*/
345351
public function getPid()
346352
{
347-
if ($this->isSigchildEnabled()) {
348-
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
349-
}
350-
351-
$this->updateStatus(false);
352-
353353
return $this->isRunning() ? $this->processInformation['pid'] : null;
354354
}
355355

@@ -361,7 +361,7 @@ public function getPid()
361361
* @return Process
362362
*
363363
* @throws LogicException In case the process is not running
364-
* @throws RuntimeException In case --enable-sigchild is activated
364+
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
365365
* @throws RuntimeException In case of failure
366366
*/
367367
public function signal($signal)
@@ -467,7 +467,9 @@ public function getIncrementalErrorOutput()
467467
*/
468468
public function getExitCode()
469469
{
470-
if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
470+
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
471+
$this->stop(0);
472+
471473
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
472474
}
473475

@@ -484,8 +486,6 @@ public function getExitCode()
484486
*
485487
* @return null|string A string representation for the exit status code, null if the Process is not terminated.
486488
*
487-
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
488-
*
489489
* @see http://tldp.org/LDP/abs/html/exitcodes.html
490490
* @see http://en.wikipedia.org/wiki/Unix_signal
491491
*/
@@ -522,12 +522,12 @@ public function hasBeenSignaled()
522522
{
523523
$this->requireProcessIsTerminated(__FUNCTION__);
524524

525-
if ($this->isSigchildEnabled()) {
525+
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
526+
$this->stop(0);
527+
526528
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
527529
}
528530

529-
$this->updateStatus(false);
530-
531531
return $this->processInformation['signaled'];
532532
}
533533

@@ -545,12 +545,12 @@ public function getTermSignal()
545545
{
546546
$this->requireProcessIsTerminated(__FUNCTION__);
547547

548-
if ($this->isSigchildEnabled()) {
548+
if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
549+
$this->stop(0);
550+
549551
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
550552
}
551553

552-
$this->updateStatus(false);
553-
554554
return $this->processInformation['termsig'];
555555
}
556556

@@ -567,8 +567,6 @@ public function hasBeenStopped()
567567
{
568568
$this->requireProcessIsTerminated(__FUNCTION__);
569569

570-
$this->updateStatus(false);
571-
572570
return $this->processInformation['stopped'];
573571
}
574572

@@ -585,8 +583,6 @@ public function getStopSignal()
585583
{
586584
$this->requireProcessIsTerminated(__FUNCTION__);
587585

588-
$this->updateStatus(false);
589-
590586
return $this->processInformation['stopsig'];
591587
}
592588

@@ -660,7 +656,7 @@ public function stop($timeout = 10, $signal = null)
660656
usleep(1000);
661657
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
662658

663-
if ($this->isRunning() && !$this->isSigchildEnabled()) {
659+
if ($this->isRunning()) {
664660
// Avoid exception here: process is supposed to be running, but it might have stopped just
665661
// after this line. In any case, let's silently discard the error, we cannot do anything.
666662
$this->doSignal($signal ?: 9, false);
@@ -998,9 +994,15 @@ private function getDescriptors()
998994

999995
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1000996
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
1001-
$descriptors = array_merge($descriptors, array(array('pipe', 'w')));
997+
$descriptors[3] = array('pipe', 'w');
998+
999+
$trap = '';
1000+
foreach (self::$posixSignals as $s) {
1001+
$trap .= "trap 'echo s$s >&3' $s;";
1002+
}
10021003

1003-
$this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code';
1004+
$this->commandline = $trap.'{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
1005+
$this->commandline .= 'pid=$!; echo p$pid >&3; wait $pid; code=$?; echo x$code >&3; exit $code';
10041006
}
10051007

10061008
return $descriptors;
@@ -1047,10 +1049,13 @@ protected function updateStatus($blocking)
10471049
}
10481050

10491051
$this->processInformation = proc_get_status($this->process);
1050-
$this->captureExitCode();
10511052

10521053
$this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);
10531054

1055+
if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1056+
$this->processInformation = $this->fallbackStatus + $this->processInformation;
1057+
}
1058+
10541059
if (!$this->processInformation['running']) {
10551060
$this->close();
10561061
}
@@ -1067,7 +1072,7 @@ protected function isSigchildEnabled()
10671072
return self::$sigchild;
10681073
}
10691074

1070-
if (!function_exists('phpinfo')) {
1075+
if (!function_exists('phpinfo') || defined('HHVM_VERSION')) {
10711076
return self::$sigchild = false;
10721077
}
10731078

@@ -1093,24 +1098,24 @@ private function readPipes($blocking, $close)
10931098

10941099
$callback = $this->callback;
10951100
foreach ($result as $type => $data) {
1096-
if (3 == $type) {
1097-
$this->fallbackExitcode = (int) $data;
1101+
if (3 === $type) {
1102+
foreach (explode("\n", substr($data, 0, -1)) as $data) {
1103+
if ('p' === $data[0]) {
1104+
$this->fallbackStatus['pid'] = (int) substr($data, 1);
1105+
} elseif ('s' === $data[0]) {
1106+
$this->fallbackStatus['signaled'] = true;
1107+
$this->fallbackStatus['exitcode'] = -1;
1108+
$this->fallbackStatus['termsig'] = (int) substr($data, 1);
1109+
} elseif ('x' === $data[0] && !isset($this->fallbackStatus['signaled'])) {
1110+
$this->fallbackStatus['exitcode'] = (int) substr($data, 1);
1111+
}
1112+
}
10981113
} else {
10991114
$callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
11001115
}
11011116
}
11021117
}
11031118

1104-
/**
1105-
* Captures the exitcode if mentioned in the process information.
1106-
*/
1107-
private function captureExitCode()
1108-
{
1109-
if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) {
1110-
$this->exitcode = $this->processInformation['exitcode'];
1111-
}
1112-
}
1113-
11141119
/**
11151120
* Closes process resource, closes file handles, sets the exitcode.
11161121
*
@@ -1120,19 +1125,19 @@ private function close()
11201125
{
11211126
$this->processPipes->close();
11221127
if (is_resource($this->process)) {
1123-
$exitcode = proc_close($this->process);
1124-
} else {
1125-
$exitcode = -1;
1128+
proc_close($this->process);
11261129
}
1127-
1128-
$this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);
1130+
$this->exitcode = $this->processInformation['exitcode'];
11291131
$this->status = self::STATUS_TERMINATED;
11301132

1131-
if (-1 === $this->exitcode && null !== $this->fallbackExitcode) {
1132-
$this->exitcode = $this->fallbackExitcode;
1133-
} elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1134-
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1135-
$this->exitcode = 128 + $this->processInformation['termsig'];
1133+
if (-1 === $this->exitcode) {
1134+
if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1135+
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1136+
$this->exitcode = 128 + $this->processInformation['termsig'];
1137+
} elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1138+
$this->processInformation['signaled'] = true;
1139+
$this->processInformation['termsig'] = -1;
1140+
}
11361141
}
11371142

11381143
// Free memory from self-reference callback created by buildCallback
@@ -1151,7 +1156,7 @@ private function resetProcessData()
11511156
$this->starttime = null;
11521157
$this->callback = null;
11531158
$this->exitcode = null;
1154-
$this->fallbackExitcode = null;
1159+
$this->fallbackStatus = array();
11551160
$this->processInformation = null;
11561161
$this->stdout = null;
11571162
$this->stderr = null;
@@ -1171,7 +1176,7 @@ private function resetProcessData()
11711176
* @return bool True if the signal was sent successfully, false otherwise
11721177
*
11731178
* @throws LogicException In case the process is not running
1174-
* @throws RuntimeException In case --enable-sigchild is activated
1179+
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
11751180
* @throws RuntimeException In case of failure
11761181
*/
11771182
private function doSignal($signal, $throwException)
@@ -1184,9 +1189,9 @@ private function doSignal($signal, $throwException)
11841189
return false;
11851190
}
11861191

1187-
if ($this->isSigchildEnabled()) {
1192+
if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled() && !isset(self::$posixSignals[$signal]) && !(function_exists('posix_kill') && @posix_kill($this->getPid(), $signal))) {
11881193
if ($throwException) {
1189-
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
1194+
throw new RuntimeException('This PHP has been compiled with --enable-sigchild and posix_kill() is not available.');
11901195
}
11911196

11921197
return false;
@@ -1211,7 +1216,10 @@ private function doSignal($signal, $throwException)
12111216
return false;
12121217
}
12131218

1214-
$this->latestSignal = $signal;
1219+
$this->latestSignal = (int) $signal;
1220+
$this->fallbackStatus['signaled'] = true;
1221+
$this->fallbackStatus['exitcode'] = -1;
1222+
$this->fallbackStatus['termsig'] = $this->latestSignal;
12151223

12161224
return true;
12171225
}

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

+5-9
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,20 @@ public function testNonBlockingWorks()
3030

3131
public function testCommandLine()
3232
{
33-
if ('phpdbg' === PHP_SAPI) {
34-
$this->markTestSkipped('phpdbg SAPI is not supported by this test.');
35-
}
36-
3733
$process = new PhpProcess(<<<PHP
3834
<?php echo 'foobar';
3935
PHP
4036
);
4137

42-
$f = new PhpExecutableFinder();
43-
$commandLine = $f->find();
38+
$commandLine = $process->getCommandLine();
4439

45-
$this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP before start');
40+
$f = new PhpExecutableFinder();
41+
$this->assertContains($f->find(), $commandLine, '::getCommandLine() returns the command line of PHP before start');
4642

4743
$process->start();
48-
$this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
44+
$this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
4945

5046
$process->wait();
51-
$this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
47+
$this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
5248
}
5349
}

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

-22
This file was deleted.

0 commit comments

Comments
 (0)