Skip to content

Commit 0de8bc2

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: Fix GH-11498: SIGCHLD is not always returned from proc_open
2 parents 14a868b + 022b6aa commit 0de8bc2

File tree

2 files changed

+84
-6
lines changed

2 files changed

+84
-6
lines changed

ext/pcntl/pcntl.c

+49-6
Original file line numberDiff line numberDiff line change
@@ -1055,21 +1055,64 @@ static void pcntl_signal_handler(int signo)
10551055
/* oops, too many signals for us to track, so we'll forget about this one */
10561056
return;
10571057
}
1058-
PCNTL_G(spares) = psig->next;
10591058

1060-
psig->signo = signo;
1061-
psig->next = NULL;
1059+
struct php_pcntl_pending_signal *psig_first = psig;
1060+
1061+
/* Standard signals may be merged into a single one.
1062+
* POSIX specifies that SIGCHLD has the si_pid field (https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html),
1063+
* so we'll handle the merging for that signal.
1064+
* See also: https://www.gnu.org/software/libc/manual/html_node/Merged-Signals.html */
1065+
if (signo == SIGCHLD) {
1066+
/* Note: The first waitpid result is not necessarily the pid that was passed above!
1067+
* We therefore cannot avoid the first waitpid() call. */
1068+
int status;
1069+
pid_t pid;
1070+
while (true) {
1071+
do {
1072+
errno = 0;
1073+
/* Although Linux specifies that WNOHANG will never result in EINTR, POSIX doesn't say so:
1074+
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html */
1075+
pid = waitpid(WAIT_ANY, &status, WNOHANG);
1076+
} while (pid <= 0 && errno == EINTR);
1077+
if (pid <= 0) {
1078+
if (UNEXPECTED(psig == psig_first)) {
1079+
/* Don't handle multiple, revert back to the single signal handling. */
1080+
goto single_signal;
1081+
}
1082+
break;
1083+
}
1084+
1085+
psig->signo = signo;
10621086

10631087
#ifdef HAVE_STRUCT_SIGINFO_T
1064-
psig->siginfo = *siginfo;
1088+
psig->siginfo = *siginfo;
1089+
psig->siginfo.si_pid = pid;
10651090
#endif
10661091

1092+
if (EXPECTED(psig->next)) {
1093+
psig = psig->next;
1094+
} else {
1095+
break;
1096+
}
1097+
}
1098+
} else {
1099+
single_signal:;
1100+
psig->signo = signo;
1101+
1102+
#ifdef HAVE_STRUCT_SIGINFO_T
1103+
psig->siginfo = *siginfo;
1104+
#endif
1105+
}
1106+
1107+
PCNTL_G(spares) = psig->next;
1108+
psig->next = NULL;
1109+
10671110
/* the head check is important, as the tick handler cannot atomically clear both
10681111
* the head and tail */
10691112
if (PCNTL_G(head) && PCNTL_G(tail)) {
1070-
PCNTL_G(tail)->next = psig;
1113+
PCNTL_G(tail)->next = psig_first;
10711114
} else {
1072-
PCNTL_G(head) = psig;
1115+
PCNTL_G(head) = psig_first;
10731116
}
10741117
PCNTL_G(tail) = psig;
10751118
PCNTL_G(pending_signals) = 1;

ext/pcntl/tests/gh11498.phpt

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
GH-11498 (SIGCHLD is not always returned from proc_open)
3+
--EXTENSIONS--
4+
pcntl
5+
--SKIPIF--
6+
<?php
7+
if (PHP_OS != 'Linux') {
8+
die('skip Linux only');
9+
}
10+
?>
11+
--FILE--
12+
<?php
13+
$processes = [];
14+
15+
pcntl_async_signals(true);
16+
pcntl_signal(SIGCHLD, function($sig, $info) use (&$processes) {
17+
unset($processes[$info['pid']]);
18+
}, false);
19+
20+
foreach (range(0, 5) as $i) {
21+
$process = proc_open('echo $$ > /dev/null', [], $pipes);
22+
$pid = proc_get_status($process)['pid'];
23+
$processes[$pid] = $process;
24+
}
25+
26+
$iters = 50;
27+
while (!empty($processes) && $iters > 0) {
28+
usleep(100_000);
29+
$iters--;
30+
}
31+
32+
var_dump(empty($processes));
33+
?>
34+
--EXPECT--
35+
bool(true)

0 commit comments

Comments
 (0)