From 7e2c857ee885cada866ae5c9a43613f002ec11c4 Mon Sep 17 00:00:00 2001 From: Lucas Bustamante Date: Wed, 7 Feb 2024 00:44:15 -0300 Subject: [PATCH 1/3] [Process] Fix Inconsistent Exit Status in proc_get_status for PHP Versions Below 8.3 --- Process.php | 14 +++++++++++ Tests/ProcessTest.php | 55 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Process.php b/Process.php index 2b6ed9ef..a4b0a784 100644 --- a/Process.php +++ b/Process.php @@ -80,6 +80,7 @@ class Process implements \IteratorAggregate private $processPipes; private $latestSignal; + private $cachedExitCode; private static $sigchild; @@ -1345,6 +1346,19 @@ protected function updateStatus(bool $blocking) $this->processInformation = proc_get_status($this->process); $running = $this->processInformation['running']; + // In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call. + // Subsequent calls return -1 as the process is discarded. This workaround caches the first + // retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior. + if (\PHP_VERSION_ID < 80300) { + if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) { + $this->cachedExitCode = $this->processInformation['exitcode']; + } + + if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) { + $this->processInformation['exitcode'] = $this->cachedExitCode; + } + } + $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); if ($this->fallbackStatus && $this->isSigchildEnabled()) { diff --git a/Tests/ProcessTest.php b/Tests/ProcessTest.php index daf842e1..059d59a4 100644 --- a/Tests/ProcessTest.php +++ b/Tests/ProcessTest.php @@ -1541,6 +1541,60 @@ public function testEnvCaseInsensitiveOnWindows() } } + public function testMultipleCallsToProcGetStatus() + { + $process = $this->getProcess('echo foo'); + $process->start(static function () use ($process) { + return $process->isRunning(); + }); + while ($process->isRunning()) { + usleep(1000); + } + $this->assertSame(0, $process->getExitCode()); + } + + public function testFailingProcessWithMultipleCallsToProcGetStatus() + { + $process = $this->getProcess('exit 123'); + $process->start(static function () use ($process) { + return $process->isRunning(); + }); + while ($process->isRunning()) { + usleep(1000); + } + $this->assertSame(123, $process->getExitCode()); + } + + /** + * @group slow + */ + public function testLongRunningProcessWithMultipleCallsToProcGetStatus() + { + $process = $this->getProcess('php -r "sleep(1); echo \'done\';"'); + $process->start(static function () use ($process) { + return $process->isRunning(); + }); + while ($process->isRunning()) { + usleep(1000); + } + $this->assertSame(0, $process->getExitCode()); + } + + /** + * @group slow + */ + public function testLongRunningProcessWithMultipleCallsToProcGetStatusError() + { + $process = $this->getProcess('php -r "sleep(1); echo \'failure\'; exit(123);"'); + $process->start(static function () use ($process) { + return $process->isRunning(); + }); + while ($process->isRunning()) { + usleep(1000); + } + $this->assertSame(123, $process->getExitCode()); + } + /** * @group transient-on-windows */ @@ -1556,7 +1610,6 @@ public function testNotTerminableInputPipe() /** * @param string|array $commandline - * @param mixed $input */ private function getProcess($commandline, ?string $cwd = null, ?array $env = null, $input = null, ?int $timeout = 60): Process { From 4fdf34004f149cc20b2f51d7d119aa500caad975 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:55:24 +0100 Subject: [PATCH 2/3] [Process] Fix failing tests causing segfaults --- Tests/ProcessTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/ProcessTest.php b/Tests/ProcessTest.php index 059d59a4..a2e370de 100644 --- a/Tests/ProcessTest.php +++ b/Tests/ProcessTest.php @@ -1570,7 +1570,7 @@ public function testFailingProcessWithMultipleCallsToProcGetStatus() */ public function testLongRunningProcessWithMultipleCallsToProcGetStatus() { - $process = $this->getProcess('php -r "sleep(1); echo \'done\';"'); + $process = $this->getProcess('sleep 1 && echo "done" && php -r "exit(0);"'); $process->start(static function () use ($process) { return $process->isRunning(); }); @@ -1585,7 +1585,7 @@ public function testLongRunningProcessWithMultipleCallsToProcGetStatus() */ public function testLongRunningProcessWithMultipleCallsToProcGetStatusError() { - $process = $this->getProcess('php -r "sleep(1); echo \'failure\'; exit(123);"'); + $process = $this->getProcess('sleep 1 && echo "failure" && php -r "exit(123);"'); $process->start(static function () use ($process) { return $process->isRunning(); }); From 710e27879e9be3395de2b98da3f52a946039f297 Mon Sep 17 00:00:00 2001 From: Kay Wei Date: Tue, 20 Feb 2024 16:24:14 +0800 Subject: [PATCH 3/3] Fix the `command -v` exception when the command option with a dash prefix --- ExecutableFinder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExecutableFinder.php b/ExecutableFinder.php index cc789603..8c7bf58d 100644 --- a/ExecutableFinder.php +++ b/ExecutableFinder.php @@ -72,7 +72,7 @@ public function find(string $name, ?string $default = null, array $extraDirs = [ } } - $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v'; + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; if (\function_exists('exec') && ($executablePath = strtok(@exec($command.' '.escapeshellarg($name)), \PHP_EOL)) && @is_executable($executablePath)) { return $executablePath; }