Skip to content

Commit

Permalink
[Process] Fix Inconsistent Exit Status in proc_get_status for PHP Ver…
Browse files Browse the repository at this point in the history
…sions Below 8.3
  • Loading branch information
Luc45 authored and nicolas-grekas committed Feb 9, 2024
1 parent cbc28e3 commit 7e2c857
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class Process implements \IteratorAggregate
private $processPipes;

private $latestSignal;
private $cachedExitCode;

private static $sigchild;

Expand Down Expand Up @@ -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()) {
Expand Down
55 changes: 54 additions & 1 deletion Tests/ProcessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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
{
Expand Down

0 comments on commit 7e2c857

Please sign in to comment.