-
Notifications
You must be signed in to change notification settings - Fork 8k
Open
Description
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);
}
}
}
}
<?phpResulted 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.net/proc_get_status — PHP 8.3 cached field documentation
- php/php-src#10250
- php/php-src#10239
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)
Reactions are currently unavailable