Skip to content

Commit d1a178a

Browse files
[Process] More robustness and deterministic tests
1 parent 337957a commit d1a178a

File tree

4 files changed

+147
-173
lines changed

4 files changed

+147
-173
lines changed

src/Symfony/Component/Process/PhpProcess.php

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ public function __construct($script, $cwd = null, array $env = null, $timeout =
4646
$php .= ' '.ProcessUtils::escapeArgument($file);
4747
$script = null;
4848
}
49+
if ('\\' !== DIRECTORY_SEPARATOR && null !== $php) {
50+
// exec is mandatory to deal with sending a signal to the process
51+
// see https://github.com/symfony/symfony/issues/5030 about prepending
52+
// command with exec
53+
$php = 'exec '.$php;
54+
}
4955

5056
parent::__construct($php, $cwd, $env, $script, $timeout, $options);
5157
}

src/Symfony/Component/Process/Process.php

+39-50
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ class Process
6666

6767
private static $sigchild;
6868
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
69+
1, // SIGHUP
70+
2, // SIGINT
71+
3, // SIGQUIT
72+
6, // SIGABRT
73+
14, // SIGALRM
74+
15, // SIGTERM
7575
);
7676

7777
/**
@@ -242,27 +242,34 @@ public function start($callback = null)
242242
if (!isset($this->options['bypass_shell'])) {
243243
$this->options['bypass_shell'] = true;
244244
}
245-
}
245+
} elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
246+
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
247+
$descriptors[3] = array('pipe', 'w');
248+
249+
$commandline = '';
250+
foreach (self::$posixSignals as $s) {
251+
$commandline .= "trap 'echo s$s >&3' $s;";
252+
}
246253

247-
$ptsWorkaround = null;
254+
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
255+
$commandline .= '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
256+
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo x$code >&3; exit $code';
248257

249-
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
250258
// Workaround for the bug, when PTS functionality is enabled.
251259
// @see : https://bugs.php.net/69442
252260
$ptsWorkaround = fopen(__FILE__, 'r');
253261
}
254262

255263
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
256264

257-
if ($ptsWorkaround) {
258-
fclose($ptsWorkaround);
259-
}
260-
261265
if (!is_resource($this->process)) {
262266
throw new RuntimeException('Unable to launch a new process.');
263267
}
264268
$this->status = self::STATUS_STARTED;
265269

270+
if (isset($descriptors[3])) {
271+
$this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
272+
}
266273
$this->processPipes->unblock();
267274

268275
if ($this->tty) {
@@ -468,8 +475,6 @@ public function getIncrementalErrorOutput()
468475
public function getExitCode()
469476
{
470477
if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
471-
$this->stop(0);
472-
473478
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
474479
}
475480

@@ -523,8 +528,6 @@ public function hasBeenSignaled()
523528
$this->requireProcessIsTerminated(__FUNCTION__);
524529

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

@@ -546,8 +549,6 @@ public function getTermSignal()
546549
$this->requireProcessIsTerminated(__FUNCTION__);
547550

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

@@ -990,22 +991,8 @@ public function checkTimeout()
990991
private function getDescriptors()
991992
{
992993
$this->processPipes = new ProcessPipes($this->useFileHandles, $this->tty);
993-
$descriptors = $this->processPipes->getDescriptors();
994-
995-
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
996-
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
997-
$descriptors[3] = array('pipe', 'w');
998-
999-
$trap = '';
1000-
foreach (self::$posixSignals as $s) {
1001-
$trap .= "trap 'echo s$s >&3' $s;";
1002-
}
1003994

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';
1006-
}
1007-
1008-
return $descriptors;
995+
return $this->processPipes->getDescriptors();
1009996
}
1010997

1011998
/**
@@ -1106,8 +1093,11 @@ private function readPipes($blocking, $close)
11061093
$this->fallbackStatus['signaled'] = true;
11071094
$this->fallbackStatus['exitcode'] = -1;
11081095
$this->fallbackStatus['termsig'] = (int) substr($data, 1);
1109-
} elseif ('x' === $data[0] && !isset($this->fallbackStatus['signaled'])) {
1110-
$this->fallbackStatus['exitcode'] = (int) substr($data, 1);
1096+
} elseif ('x' === $data[0]) {
1097+
$this->fallbackStatus['running'] = false;
1098+
if (!isset($this->fallbackStatus['signaled'])) {
1099+
$this->fallbackStatus['exitcode'] = (int) substr($data, 1);
1100+
}
11111101
}
11121102
}
11131103
} else {
@@ -1189,14 +1179,6 @@ private function doSignal($signal, $throwException)
11891179
return false;
11901180
}
11911181

1192-
if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled() && !isset(self::$posixSignals[$signal]) && !(function_exists('posix_kill') && @posix_kill($this->getPid(), $signal))) {
1193-
if ($throwException) {
1194-
throw new RuntimeException('This PHP has been compiled with --enable-sigchild and posix_kill() is not available.');
1195-
}
1196-
1197-
return false;
1198-
}
1199-
12001182
if ('\\' === DIRECTORY_SEPARATOR) {
12011183
exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode);
12021184
if ($exitCode && $this->isRunning()) {
@@ -1206,14 +1188,21 @@ private function doSignal($signal, $throwException)
12061188

12071189
return false;
12081190
}
1209-
}
1210-
1211-
if (true !== @proc_terminate($this->process, $signal) && '\\' !== DIRECTORY_SEPARATOR) {
1212-
if ($throwException) {
1213-
throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1191+
} else {
1192+
if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
1193+
$ok = @proc_terminate($this->process, $signal);
1194+
} elseif (function_exists('posix_kill')) {
1195+
$ok = @posix_kill($this->getPid(), $signal);
1196+
} elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $this->getPid()), array(2 => array('pipe', 'w')), $pipes)) {
1197+
$ok = false === fgets($pipes[2]);
12141198
}
1199+
if (!$ok) {
1200+
if ($throwException) {
1201+
throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1202+
}
12151203

1216-
return false;
1204+
return false;
1205+
}
12171206
}
12181207

12191208
$this->latestSignal = (int) $signal;

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,18 @@ function handleSignal($signal)
3030
break;
3131
}
3232

33-
echo "received signal $name\n";
33+
echo "signal $name\n";
3434
}
3535

36-
declare (ticks = 1);
3736
pcntl_signal(SIGTERM, 'handleSignal');
3837
pcntl_signal(SIGINT, 'handleSignal');
3938

39+
echo 'received ';
40+
4041
$duration = isset($argv[1]) ? (int) $argv[1] : 3;
4142
$start = microtime(true);
4243

4344
while ($duration > (microtime(true) - $start)) {
44-
usleep(1000);
45+
usleep(10000);
46+
pcntl_signal_dispatch();
4547
}

0 commit comments

Comments
 (0)