I'm having troubles with using proc_get_status after a pcntl_fork in the parent process.
Here's an example with docker and PHP 7.4, but note the PHP version does not matter, the result is the same from at least PHP 7.2 to PHP 8.1.
Setup
Dockerfile
FROM php:7.4
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions pcntl
Run: docker build -t php-pcntl-7.4 .
test.php
<?php
if ($argv[1] ?? false) {
pcntl_async_signals(true);
pcntl_signal(SIGCHLD, SIG_IGN);
$pid = pcntl_fork();
if ($pid === -1) {
throw new \RuntimeException('fork failed');
}
if ($pid === 0) {
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
throw new \Exception('child should be ignored');
}
pcntl_waitpid($pid, $childStatusCode);
}
function runCommand(string $command): void
{
$pipes = [];
$proc = proc_open($command, [['pipe', 'r'],['pipe', 'w'],['pipe', 'w']], $pipes);
do {
if (isset($status)) {
usleep(300000);
}
$status = proc_get_status($proc);
} while ($status['running']);
echo 'Command: ' . $command . PHP_EOL;
echo 'last proc_get_status exitcode: ' . $status['exitcode'] . PHP_EOL;
echo PHP_EOL;
}
echo 'PHP version: ' . PHP_VERSION . PHP_EOL;
runCommand('/bin/echo test');
runCommand('/bin/false');
Test without running a fork before
Run: docker run -it --rm -v $PWD:/workdir -w /workdir php-pcntl-7.4 php test.php
Output:
PHP version: 7.4.28
Command: /bin/echo test
last proc_get_status exitcode: 0
Command: /bin/false
last proc_get_status exitcode: 1
Test with running a fork before
Run: docker run -it --rm -v $PWD:/workdir -w /workdir php-pcntl-7.4 php test.php 1
Output:
PHP version: 7.4.28
Command: /bin/echo test
last proc_get_status exitcode: -1
Command: /bin/false
last proc_get_status exitcode: -1
Does anyone have an idea of why the exitcode is always -1, and if there is a workaround ?
This seems to be a bug in the pcntl_signal(SIGCHLD, SIG_IGN) call. Remove the call and the problem goes away.
As SIG_IGN is the default for SIGCHLD, it's rarely needed which is probably why this hasn't been noticed before.