Skip to content

Commit bd38abd

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

8 files changed

+123
-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 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

+57-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,7 @@ public function getIncrementalErrorOutput()
467467
*/
468468
public function getExitCode()
469469
{
470-
if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
470+
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
471471
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
472472
}
473473

@@ -484,8 +484,6 @@ public function getExitCode()
484484
*
485485
* @return null|string A string representation for the exit status code, null if the Process is not terminated.
486486
*
487-
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
488-
*
489487
* @see http://tldp.org/LDP/abs/html/exitcodes.html
490488
* @see http://en.wikipedia.org/wiki/Unix_signal
491489
*/
@@ -522,12 +520,10 @@ public function hasBeenSignaled()
522520
{
523521
$this->requireProcessIsTerminated(__FUNCTION__);
524522

525-
if ($this->isSigchildEnabled()) {
523+
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
526524
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
527525
}
528526

529-
$this->updateStatus(false);
530-
531527
return $this->processInformation['signaled'];
532528
}
533529

@@ -545,12 +541,10 @@ public function getTermSignal()
545541
{
546542
$this->requireProcessIsTerminated(__FUNCTION__);
547543

548-
if ($this->isSigchildEnabled()) {
544+
if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
549545
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
550546
}
551547

552-
$this->updateStatus(false);
553-
554548
return $this->processInformation['termsig'];
555549
}
556550

@@ -567,8 +561,6 @@ public function hasBeenStopped()
567561
{
568562
$this->requireProcessIsTerminated(__FUNCTION__);
569563

570-
$this->updateStatus(false);
571-
572564
return $this->processInformation['stopped'];
573565
}
574566

@@ -585,8 +577,6 @@ public function getStopSignal()
585577
{
586578
$this->requireProcessIsTerminated(__FUNCTION__);
587579

588-
$this->updateStatus(false);
589-
590580
return $this->processInformation['stopsig'];
591581
}
592582

@@ -660,7 +650,7 @@ public function stop($timeout = 10, $signal = null)
660650
usleep(1000);
661651
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
662652

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

999989
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1000990
// 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')));
991+
$descriptors[3] = array('pipe', 'w');
992+
993+
$trap = '';
994+
foreach (self::$posixSignals as $s) {
995+
$trap .= "trap 'echo s$s >&3' $s;";
996+
}
1002997

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

10061002
return $descriptors;
@@ -1047,10 +1043,13 @@ protected function updateStatus($blocking)
10471043
}
10481044

10491045
$this->processInformation = proc_get_status($this->process);
1050-
$this->captureExitCode();
10511046

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

1049+
if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1050+
$this->processInformation = $this->fallbackStatus + $this->processInformation;
1051+
}
1052+
10541053
if (!$this->processInformation['running']) {
10551054
$this->close();
10561055
}
@@ -1067,7 +1066,7 @@ protected function isSigchildEnabled()
10671066
return self::$sigchild;
10681067
}
10691068

1070-
if (!function_exists('phpinfo')) {
1069+
if (!function_exists('phpinfo') || defined('HHVM_VERSION')) {
10711070
return self::$sigchild = false;
10721071
}
10731072

@@ -1093,24 +1092,24 @@ private function readPipes($blocking, $close)
10931092

10941093
$callback = $this->callback;
10951094
foreach ($result as $type => $data) {
1096-
if (3 == $type) {
1097-
$this->fallbackExitcode = (int) $data;
1095+
if (3 === $type) {
1096+
foreach (explode("\n", substr($data, 0, -1)) as $data) {
1097+
if ('p' === $data[0]) {
1098+
$this->fallbackStatus['pid'] = (int) substr($data, 1);
1099+
} elseif ('s' === $data[0]) {
1100+
$this->fallbackStatus['signaled'] = true;
1101+
$this->fallbackStatus['exitcode'] = -1;
1102+
$this->fallbackStatus['termsig'] = (int) substr($data, 1);
1103+
} elseif ('x' === $data[0] && !isset($this->fallbackStatus['signaled'])) {
1104+
$this->fallbackStatus['exitcode'] = (int) substr($data, 1);
1105+
}
1106+
}
10981107
} else {
10991108
$callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
11001109
}
11011110
}
11021111
}
11031112

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-
11141113
/**
11151114
* Closes process resource, closes file handles, sets the exitcode.
11161115
*
@@ -1120,19 +1119,19 @@ private function close()
11201119
{
11211120
$this->processPipes->close();
11221121
if (is_resource($this->process)) {
1123-
$exitcode = proc_close($this->process);
1124-
} else {
1125-
$exitcode = -1;
1122+
proc_close($this->process);
11261123
}
1127-
1128-
$this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);
1124+
$this->exitcode = $this->processInformation['exitcode'];
11291125
$this->status = self::STATUS_TERMINATED;
11301126

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'];
1127+
if (-1 === $this->exitcode) {
1128+
if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1129+
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1130+
$this->exitcode = 128 + $this->processInformation['termsig'];
1131+
} elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1132+
$this->processInformation['signaled'] = true;
1133+
$this->processInformation['termsig'] = -1;
1134+
}
11361135
}
11371136

11381137
// Free memory from self-reference callback created by buildCallback
@@ -1151,7 +1150,7 @@ private function resetProcessData()
11511150
$this->starttime = null;
11521151
$this->callback = null;
11531152
$this->exitcode = null;
1154-
$this->fallbackExitcode = null;
1153+
$this->fallbackStatus = array();
11551154
$this->processInformation = null;
11561155
$this->stdout = null;
11571156
$this->stderr = null;
@@ -1171,7 +1170,7 @@ private function resetProcessData()
11711170
* @return bool True if the signal was sent successfully, false otherwise
11721171
*
11731172
* @throws LogicException In case the process is not running
1174-
* @throws RuntimeException In case --enable-sigchild is activated
1173+
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
11751174
* @throws RuntimeException In case of failure
11761175
*/
11771176
private function doSignal($signal, $throwException)
@@ -1184,9 +1183,9 @@ private function doSignal($signal, $throwException)
11841183
return false;
11851184
}
11861185

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

11921191
return false;
@@ -1211,7 +1210,10 @@ private function doSignal($signal, $throwException)
12111210
return false;
12121211
}
12131212

1214-
$this->latestSignal = $signal;
1213+
$this->latestSignal = (int) $signal;
1214+
$this->fallbackStatus['signaled'] = true;
1215+
$this->fallbackStatus['exitcode'] = -1;
1216+
$this->fallbackStatus['termsig'] = $this->latestSignal;
12151217

12161218
return true;
12171219
}

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)