Skip to content

Commit 7e2c857

Browse files
Luc45nicolas-grekas
authored andcommitted
[Process] Fix Inconsistent Exit Status in proc_get_status for PHP Versions Below 8.3
1 parent cbc28e3 commit 7e2c857

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

Process.php

+14
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class Process implements \IteratorAggregate
8080
private $processPipes;
8181

8282
private $latestSignal;
83+
private $cachedExitCode;
8384

8485
private static $sigchild;
8586

@@ -1345,6 +1346,19 @@ protected function updateStatus(bool $blocking)
13451346
$this->processInformation = proc_get_status($this->process);
13461347
$running = $this->processInformation['running'];
13471348

1349+
// In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call.
1350+
// Subsequent calls return -1 as the process is discarded. This workaround caches the first
1351+
// retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior.
1352+
if (\PHP_VERSION_ID < 80300) {
1353+
if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) {
1354+
$this->cachedExitCode = $this->processInformation['exitcode'];
1355+
}
1356+
1357+
if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) {
1358+
$this->processInformation['exitcode'] = $this->cachedExitCode;
1359+
}
1360+
}
1361+
13481362
$this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
13491363

13501364
if ($this->fallbackStatus && $this->isSigchildEnabled()) {

Tests/ProcessTest.php

+54-1
Original file line numberDiff line numberDiff line change
@@ -1541,6 +1541,60 @@ public function testEnvCaseInsensitiveOnWindows()
15411541
}
15421542
}
15431543

1544+
public function testMultipleCallsToProcGetStatus()
1545+
{
1546+
$process = $this->getProcess('echo foo');
1547+
$process->start(static function () use ($process) {
1548+
return $process->isRunning();
1549+
});
1550+
while ($process->isRunning()) {
1551+
usleep(1000);
1552+
}
1553+
$this->assertSame(0, $process->getExitCode());
1554+
}
1555+
1556+
public function testFailingProcessWithMultipleCallsToProcGetStatus()
1557+
{
1558+
$process = $this->getProcess('exit 123');
1559+
$process->start(static function () use ($process) {
1560+
return $process->isRunning();
1561+
});
1562+
while ($process->isRunning()) {
1563+
usleep(1000);
1564+
}
1565+
$this->assertSame(123, $process->getExitCode());
1566+
}
1567+
1568+
/**
1569+
* @group slow
1570+
*/
1571+
public function testLongRunningProcessWithMultipleCallsToProcGetStatus()
1572+
{
1573+
$process = $this->getProcess('php -r "sleep(1); echo \'done\';"');
1574+
$process->start(static function () use ($process) {
1575+
return $process->isRunning();
1576+
});
1577+
while ($process->isRunning()) {
1578+
usleep(1000);
1579+
}
1580+
$this->assertSame(0, $process->getExitCode());
1581+
}
1582+
1583+
/**
1584+
* @group slow
1585+
*/
1586+
public function testLongRunningProcessWithMultipleCallsToProcGetStatusError()
1587+
{
1588+
$process = $this->getProcess('php -r "sleep(1); echo \'failure\'; exit(123);"');
1589+
$process->start(static function () use ($process) {
1590+
return $process->isRunning();
1591+
});
1592+
while ($process->isRunning()) {
1593+
usleep(1000);
1594+
}
1595+
$this->assertSame(123, $process->getExitCode());
1596+
}
1597+
15441598
/**
15451599
* @group transient-on-windows
15461600
*/
@@ -1556,7 +1610,6 @@ public function testNotTerminableInputPipe()
15561610

15571611
/**
15581612
* @param string|array $commandline
1559-
* @param mixed $input
15601613
*/
15611614
private function getProcess($commandline, ?string $cwd = null, ?array $env = null, $input = null, ?int $timeout = 60): Process
15621615
{

0 commit comments

Comments
 (0)