Skip to content

Commit f826bbd

Browse files
committed
Merge branch 'PHP-7.4'
2 parents 5b9725e + 29c7c9e commit f826bbd

File tree

5 files changed

+140
-12
lines changed

5 files changed

+140
-12
lines changed

sapi/fpm/fpm/fpm_children.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,11 @@ int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to
404404
return 2;
405405
}
406406

407+
zlog(ZLOG_DEBUG, "blocking signals before child birth");
408+
if (0 > fpm_signals_child_block()) {
409+
zlog(ZLOG_WARNING, "child may miss signals");
410+
}
411+
407412
pid = fork();
408413

409414
switch (pid) {
@@ -415,12 +420,16 @@ int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to
415420
return 0;
416421

417422
case -1 :
423+
zlog(ZLOG_DEBUG, "unblocking signals");
424+
fpm_signals_unblock();
418425
zlog(ZLOG_SYSERROR, "fork() failed");
419426

420427
fpm_resources_discard(child);
421428
return 2;
422429

423430
default :
431+
zlog(ZLOG_DEBUG, "unblocking signals, child born");
432+
fpm_signals_unblock();
424433
child->pid = pid;
425434
fpm_clock_get(&child->started);
426435
fpm_parent_resources_use(child);

sapi/fpm/fpm/fpm_main.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,11 +1570,7 @@ int main(int argc, char *argv[])
15701570
does that for us! thies@thieso.net
15711571
20000419 */
15721572

1573-
/* Subset of signals from fpm_signals_init_main() to avoid unexpected death during early init
1574-
or during reload just after execvp() or fork */
1575-
int init_signal_array[] = { SIGUSR1, SIGUSR2, SIGCHLD };
1576-
if (0 > fpm_signals_init_mask(init_signal_array, sizeof(init_signal_array)/sizeof(init_signal_array[0])) ||
1577-
0 > fpm_signals_block()) {
1573+
if (0 > fpm_signals_init_mask() || 0 > fpm_signals_block()) {
15781574
zlog(ZLOG_WARNING, "Could die in the case of too early reload signal");
15791575
}
15801576
zlog(ZLOG_DEBUG, "Blocked some signals");

sapi/fpm/fpm/fpm_signals.c

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
static int sp[2];
2222
static sigset_t block_sigset;
23+
static sigset_t child_block_sigset;
2324

2425
const char *fpm_signal_names[NSIG + 1] = {
2526
#ifdef SIGHUP
@@ -166,8 +167,11 @@ static void sig_handler(int signo) /* {{{ */
166167
int saved_errno;
167168

168169
if (fpm_globals.parent_pid != getpid()) {
169-
/* prevent a signal race condition when child process
170-
have not set up it's own signal handler yet */
170+
/* Avoid using of signal handlers from the master process in a worker
171+
before the child sets up its own signal handlers.
172+
Normally it is prevented by the sigprocmask() calls
173+
around fork(). This execution branch is a last resort trap
174+
that has no protection against #76601. */
171175
return;
172176
}
173177

@@ -247,6 +251,10 @@ int fpm_signals_init_child() /* {{{ */
247251
}
248252

249253
zend_signal_init();
254+
255+
if (0 > fpm_signals_unblock()) {
256+
return -1;
257+
}
250258
return 0;
251259
}
252260
/* }}} */
@@ -257,16 +265,23 @@ int fpm_signals_get_fd() /* {{{ */
257265
}
258266
/* }}} */
259267

260-
int fpm_signals_init_mask(int *signum_array, size_t size) /* {{{ */
268+
int fpm_signals_init_mask() /* {{{ */
261269
{
270+
/* Subset of signals from fpm_signals_init_main() and fpm_got_signal()
271+
blocked to avoid unexpected death during early init
272+
or during reload just after execvp() or fork */
273+
int init_signal_array[] = { SIGUSR1, SIGUSR2, SIGCHLD };
274+
size_t size = sizeof(init_signal_array)/sizeof(init_signal_array[0]);
262275
size_t i = 0;
263-
if (0 > sigemptyset(&block_sigset)) {
276+
if (0 > sigemptyset(&block_sigset) ||
277+
0 > sigemptyset(&child_block_sigset)) {
264278
zlog(ZLOG_SYSERROR, "failed to prepare signal block mask: sigemptyset()");
265279
return -1;
266280
}
267281
for (i = 0; i < size; ++i) {
268-
int sig_i = signum_array[i];
269-
if (0 > sigaddset(&block_sigset, sig_i)) {
282+
int sig_i = init_signal_array[i];
283+
if (0 > sigaddset(&block_sigset, sig_i) ||
284+
0 > sigaddset(&child_block_sigset, sig_i)) {
270285
if (sig_i <= NSIG && fpm_signal_names[sig_i] != NULL) {
271286
zlog(ZLOG_SYSERROR, "failed to prepare signal block mask: sigaddset(%s)",
272287
fpm_signal_names[sig_i]);
@@ -276,6 +291,11 @@ int fpm_signals_init_mask(int *signum_array, size_t size) /* {{{ */
276291
return -1;
277292
}
278293
}
294+
if (0 > sigaddset(&child_block_sigset, SIGTERM) ||
295+
0 > sigaddset(&child_block_sigset, SIGQUIT)) {
296+
zlog(ZLOG_SYSERROR, "failed to prepare child signal block mask: sigaddset()");
297+
return -1;
298+
}
279299
return 0;
280300
}
281301
/* }}} */
@@ -290,6 +310,16 @@ int fpm_signals_block() /* {{{ */
290310
}
291311
/* }}} */
292312

313+
int fpm_signals_child_block() /* {{{ */
314+
{
315+
if (0 > sigprocmask(SIG_BLOCK, &child_block_sigset, NULL)) {
316+
zlog(ZLOG_SYSERROR, "failed to block child signals");
317+
return -1;
318+
}
319+
return 0;
320+
}
321+
/* }}} */
322+
293323
int fpm_signals_unblock() /* {{{ */
294324
{
295325
/* Ensure that during reload after upgrade all signals are unblocked.

sapi/fpm/fpm/fpm_signals.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
int fpm_signals_init_main();
99
int fpm_signals_init_child();
1010
int fpm_signals_get_fd();
11-
int fpm_signals_init_mask(int *signum_array, size_t size);
11+
int fpm_signals_init_mask();
1212
int fpm_signals_block();
13+
int fpm_signals_child_block();
1314
int fpm_signals_unblock();
1415

1516
extern const char *fpm_signal_names[NSIG + 1];
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
--TEST--
2+
FPM: bug76601 children should not ignore signals during concurrent reloads
3+
--SKIPIF--
4+
<?php
5+
include "skipif.inc";
6+
if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
7+
?>
8+
--FILE--
9+
<?php
10+
11+
require_once "tester.inc";
12+
13+
$cfg = <<<EOT
14+
[global]
15+
error_log = {{FILE:LOG}}
16+
pid = {{FILE:PID}}
17+
; some value twice greater than tester->getLogLines() timeout
18+
process_control_timeout=10
19+
[unconfined]
20+
listen = {{ADDR}}
21+
; spawn children immediately after reload
22+
pm = static
23+
pm.max_children = 10
24+
EOT;
25+
26+
$code = <<<EOT
27+
<?php
28+
/* empty */
29+
EOT;
30+
31+
/*
32+
* If a child miss SIGQUIT then reload process should stuck
33+
* for at least process_control_timeout that is set greater
34+
* than timout in log reading functions.
35+
*
36+
* Alternative way is to set log_level=debug and filter result of
37+
* $tester->getLogLines(2000) for lines containing SIGKILL
38+
*
39+
* [22-Oct-2019 03:28:19.532703] DEBUG: pid 21315, fpm_pctl_kill_all(), line 161: [pool unconfined] sending signal 9 SIGKILL to child 21337
40+
* [22-Oct-2019 03:28:19.533471] DEBUG: pid 21315, fpm_children_bury(), line 259: [pool unconfined] child 21337 exited on signal 9 (SIGKILL) after 1.003055 seconds from start
41+
*
42+
* but it has less probability of failure detection. Additionally it requires more
43+
* $tester->expectLogNotice() around last reload due to presence of debug messages.
44+
*/
45+
46+
$tester = new FPM\Tester($cfg, $code);
47+
$tester->start();
48+
$tester->expectLogStartNotices();
49+
50+
/* Vary interval between concurrent reload requests
51+
since performance of test instance is not known in advance */
52+
$max_interval = 25000;
53+
$step = 1000;
54+
$pid = $tester->getPid();
55+
for ($interval = 0; $interval < $max_interval; $interval += $step) {
56+
exec("kill -USR2 $pid", $out, $killExitCode);
57+
if ($killExitCode) {
58+
echo "ERROR: master process is dead\n";
59+
break;
60+
}
61+
usleep($interval);
62+
}
63+
echo "Reached interval $interval us with $step us steps\n";
64+
$tester->expectLogNotice('Reloading in progress ...');
65+
/* Consume mix of 'Reloading in progress ...' and 'reloading: .*' */
66+
$skipped = $tester->getLogLines(2000);
67+
68+
$tester->signal('USR2');
69+
$tester->expectLogNotice('Reloading in progress ...');
70+
/* When a child ignores SIGQUIT, the following expectation fails due to timeout. */
71+
if (!$tester->expectLogNotice('reloading: .*')) {
72+
/* for troubleshooting */
73+
echo "Skipped messages\n";
74+
echo implode('', $skipped);
75+
}
76+
$tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"');
77+
$tester->expectLogStartNotices();
78+
79+
$tester->terminate();
80+
$tester->expectLogTerminatingNotices();
81+
$tester->close();
82+
83+
?>
84+
Done
85+
--EXPECT--
86+
Reached interval 25000 us with 1000 us steps
87+
Done
88+
--CLEAN--
89+
<?php
90+
require_once "tester.inc";
91+
FPM\Tester::clean();
92+
?>

0 commit comments

Comments
 (0)