Description
The following code:
while ($await_processes) {
$await_processes_copy = $await_processes;
foreach ($await_processes_copy as $worker_id => $process) {
$proc_status = proc_get_status($process['process']);
if ($proc_status === false) {
throw new MultiProcessHydraException("Failed to get proc status of worker $worker_id");
}
if ($proc_status['running'] === false) {
unset($await_processes[$worker_id]);
$stdout = stream_get_contents(fopen($process['files']['stdout'], 'r'));
$stderr = stream_get_contents(fopen($process['files']['stderr'], 'r'));
$exit_code = $proc_status['exitcode'];
$log_message = [
'message' => "Awaited process",
'job_class' => $this->worker_class,
'stdout' => $stdout,
'stderr' => $stderr,
'worker_id' => $worker_id,
'exit_code' => $exit_code,
];
$logger->log($log_message);
// If any child-process exits with a non-zero code (such as a fatal, uncatchable error)
// process this individual worker as a failure, but continue to process the responses from
// each other process.
if ($exit_code !== 0) {
$worker_responses[] = [[
'error' => "Worker process $worker_id exited with non-zero exit code: $exit_code. "
. "Stderr: $stderr",
]];
} else {
$worker_responses[] = $cache->getCacheValue($process['files']['cache_file'])->asArray();
}
unset($process['files']['cache_file']);
foreach ($process['files'] as $_ => $file) {
unlink($file);
}
}
}
}
<?php
Resulted in this output:
Uncaught Exception: [MultiProcessHydraException] Worker process mp-hydra-6b0a280c-2428-41f5-b393-11e4477b4e9d-7 exited with non-zero exit code: -1.
...(multiple process exited with -1)
But I expected this output instead:
The parent process checked the exitcode of child process to be 0 and finish the job, because from our log, all the subtasks were successfully finished.
Investigation
PHP 8.3 installs an internal SIGCHLD signal handler (not present in PHP 8.1) as part of the pcntl extension. When a child process exits, this handler intercepts the SIGCHLD signal and interrupts the waitpid() syscall inside proc_get_status().
Verified by:
| Test |
PHP 8.1 |
PHP 8.3 |
| SIGCHLD disposition |
SIG_DFL |
CUSTOM_HANDLER |
| proc_open("exit(42)") + proc_get_status() |
exitcode: 42 |
exitcode: -1 |
| Same with php -n (no extensions) |
exitcode: 42 |
[Still broken — in PHP core](exitcode: -1) |
| With pcntl_signal(SIGCHLD, SIG_DFL) first |
exitcode: 42 |
exitcode: 42 |
Temporary Fix
Added pcntl_signal(SIGCHLD, SIG_DFL) before spawning workers in MultiProcessHydra::spawn(), resetting SIGCHLD to default behavior so proc_get_status() can reap children without interruption.
References:
PHP Version
PHP 8.3.25 (cli) (built: Jan 5 2026 18:10:06) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.25, Copyright (c) Zend Technologies
with Zend OPcache v8.3.25, Copyright (c), by Zend Technologies
Operating System
Amazon Linux 2 (AL2)
Description
The following code:
Resulted in this output:
But I expected this output instead:
The parent process checked the exitcode of child process to be 0 and finish the job, because from our log, all the subtasks were successfully finished.
Investigation
PHP 8.3 installs an internal SIGCHLD signal handler (not present in PHP 8.1) as part of the pcntl extension. When a child process exits, this handler intercepts the SIGCHLD signal and interrupts the waitpid() syscall inside proc_get_status().
Verified by:
Temporary Fix
Added pcntl_signal(SIGCHLD, SIG_DFL) before spawning workers in MultiProcessHydra::spawn(), resetting SIGCHLD to default behavior so proc_get_status() can reap children without interruption.
References:
PHP Version
Operating System
Amazon Linux 2 (AL2)