Skip to content

Commit f39b513

Browse files
committed
Fix GH-11498: SIGCHLD is not always returned from proc_open
Linux, and maybe other unixes, may merge multiple standard signals into a single one. This causes issues when keeping track of process IDs. Solve this by manually checking which children are dead using waitpid(). Test case is based on taka-oyama's test code. Closes GH-11509.
1 parent 1111a95 commit f39b513

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ PHP NEWS
88
- Date:
99
. Fixed bug GH-11368 (Date modify returns invalid datetime). (Derick)
1010

11+
- PCNTL:
12+
. Fixed bug GH-11498 (SIGCHLD is not always returned from proc_open).
13+
(nielsdos)
14+
1115
- PCRE:
1216
. Mangle PCRE regex cache key with JIT option. (mvorisek)
1317

ext/pcntl/pcntl.c

+49-6
Original file line numberDiff line numberDiff line change
@@ -1345,21 +1345,64 @@ static void pcntl_signal_handler(int signo)
13451345
/* oops, too many signals for us to track, so we'll forget about this one */
13461346
return;
13471347
}
1348-
PCNTL_G(spares) = psig->next;
13491348

1350-
psig->signo = signo;
1351-
psig->next = NULL;
1349+
struct php_pcntl_pending_signal *psig_first = psig;
1350+
1351+
/* Standard signals may be merged into a single one.
1352+
* POSIX specifies that SIGCHLD has the si_pid field (https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html),
1353+
* so we'll handle the merging for that signal.
1354+
* See also: https://www.gnu.org/software/libc/manual/html_node/Merged-Signals.html */
1355+
if (signo == SIGCHLD) {
1356+
/* Note: The first waitpid result is not necessarily the pid that was passed above!
1357+
* We therefore cannot avoid the first waitpid() call. */
1358+
int status;
1359+
pid_t pid;
1360+
while (true) {
1361+
do {
1362+
errno = 0;
1363+
/* Although Linux specifies that WNOHANG will never result in EINTR, POSIX doesn't say so:
1364+
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html */
1365+
pid = waitpid(WAIT_ANY, &status, WNOHANG);
1366+
} while (pid <= 0 && errno == EINTR);
1367+
if (pid <= 0) {
1368+
if (UNEXPECTED(psig == psig_first)) {
1369+
/* Don't handle multiple, revert back to the single signal handling. */
1370+
goto single_signal;
1371+
}
1372+
break;
1373+
}
1374+
1375+
psig->signo = signo;
1376+
1377+
#ifdef HAVE_STRUCT_SIGINFO_T
1378+
psig->siginfo = *siginfo;
1379+
psig->siginfo.si_pid = pid;
1380+
#endif
1381+
1382+
if (EXPECTED(psig->next)) {
1383+
psig = psig->next;
1384+
} else {
1385+
break;
1386+
}
1387+
}
1388+
} else {
1389+
single_signal:;
1390+
psig->signo = signo;
13521391

13531392
#ifdef HAVE_STRUCT_SIGINFO_T
1354-
psig->siginfo = *siginfo;
1393+
psig->siginfo = *siginfo;
13551394
#endif
1395+
}
1396+
1397+
PCNTL_G(spares) = psig->next;
1398+
psig->next = NULL;
13561399

13571400
/* the head check is important, as the tick handler cannot atomically clear both
13581401
* the head and tail */
13591402
if (PCNTL_G(head) && PCNTL_G(tail)) {
1360-
PCNTL_G(tail)->next = psig;
1403+
PCNTL_G(tail)->next = psig_first;
13611404
} else {
1362-
PCNTL_G(head) = psig;
1405+
PCNTL_G(head) = psig_first;
13631406
}
13641407
PCNTL_G(tail) = psig;
13651408
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)