-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Do not let PHP-FPM children miss SIGTERM, SIGQUIT #4836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
8ac3486
82c0c16
278f57f
067cf2e
8aad009
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
|
||
static int sp[2]; | ||
static sigset_t block_sigset; | ||
static sigset_t child_block_sigset; | ||
|
||
const char *fpm_signal_names[NSIG + 1] = { | ||
#ifdef SIGHUP | ||
|
@@ -167,7 +168,8 @@ static void sig_handler(int signo) /* {{{ */ | |
|
||
if (fpm_globals.parent_pid != getpid()) { | ||
/* prevent a signal race condition when child process | ||
have not set up it's own signal handler yet */ | ||
do not set up it's own sigprocmask for some reason, | ||
leads to #76601 in such cases */ | ||
return; | ||
} | ||
|
||
|
@@ -247,6 +249,10 @@ int fpm_signals_init_child() /* {{{ */ | |
} | ||
|
||
zend_signal_init(); | ||
|
||
if (0 > fpm_signals_unblock()) { | ||
return -1; | ||
} | ||
return 0; | ||
} | ||
/* }}} */ | ||
|
@@ -276,6 +282,12 @@ int fpm_signals_init_mask(int *signum_array, size_t size) /* {{{ */ | |
return -1; | ||
} | ||
} | ||
memcpy(&child_block_sigset, &block_sigset, sizeof(block_sigset)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is technically undefined (or at least not recommended for portability) as |
||
if (0 > sigaddset(&child_block_sigset, SIGTERM) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the actually the reason specify one part (block_sigset) from parameter provided in fpm_main.c and another part (child_block_sigset extra signals) specified directly in this function? I think we should either have everything in the parameters (meaning extra child signals to block array paramter) or just have everything defined in fpm_signals which might make more sense. |
||
0 > sigaddset(&child_block_sigset, SIGQUIT)) { | ||
zlog(ZLOG_SYSERROR, "failed to prepare child signal block mask: sigaddset()"); | ||
return -1; | ||
} | ||
return 0; | ||
} | ||
/* }}} */ | ||
|
@@ -290,6 +302,16 @@ int fpm_signals_block() /* {{{ */ | |
} | ||
/* }}} */ | ||
|
||
int fpm_signals_child_block() /* {{{ */ | ||
{ | ||
if (0 > sigprocmask(SIG_BLOCK, &child_block_sigset, NULL)) { | ||
zlog(ZLOG_SYSERROR, "failed to block child signals"); | ||
return -1; | ||
} | ||
return 0; | ||
} | ||
/* }}} */ | ||
|
||
int fpm_signals_unblock() /* {{{ */ | ||
{ | ||
/* Ensure that during reload after upgrade all signals are unblocked. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
--TEST-- | ||
FPM: bug76601 children should not ignore signals during concurrent reloads | ||
--SKIPIF-- | ||
<?php | ||
include "skipif.inc"; | ||
if (getenv("SKIP_SLOW_TESTS")) die("skip slow test"); | ||
?> | ||
--FILE-- | ||
<?php | ||
|
||
require_once "tester.inc"; | ||
|
||
$cfg = <<<EOT | ||
[global] | ||
error_log = {{FILE:LOG}} | ||
pid = {{FILE:PID}} | ||
; some value twice greater than tester->getLogLines() timeout | ||
process_control_timeout=10 | ||
[unconfined] | ||
listen = {{ADDR}} | ||
; spawn children immediately after reload | ||
pm = static | ||
pm.max_children = 10 | ||
EOT; | ||
|
||
$code = <<<EOT | ||
<?php | ||
/* empty */ | ||
EOT; | ||
|
||
/* | ||
* If a child miss SIGQUIT then reload process should stuck | ||
* for at least process_control_timeout that is set greater | ||
* than timout in log reading functions. | ||
* | ||
* Alternative way is to set log_level=debug and filter result of | ||
* $tester->getLogLines(2000) for lines containing SIGKILL | ||
* | ||
* [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 | ||
* [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 | ||
* | ||
* but it has less probability of failure detection. Additionally it requires more | ||
* $tester->expectLogNotice() around last reload due to presence of debug messages. | ||
*/ | ||
|
||
$tester = new FPM\Tester($cfg, $code); | ||
$tester->start(); | ||
$tester->expectLogStartNotices(); | ||
|
||
/* Vary interval between concurrent reload requests | ||
since performance of test instance is not known in advance */ | ||
$max_interval = 25000; | ||
$step = 1000; | ||
$pid = $tester->getPid(); | ||
for ($interval = 0; $interval < $max_interval; $interval += $step) { | ||
exec("kill -USR2 $pid", $out, $killExitCode); | ||
if ($killExitCode) { | ||
echo "ERROR: master process is dead\n"; | ||
break; | ||
} | ||
usleep($interval); | ||
} | ||
echo "Reached interval $interval us with $step us steps\n"; | ||
$tester->expectLogNotice('Reloading in progress ...'); | ||
/* Consume mix of 'Reloading in progress ...' and 'reloading: .*' */ | ||
$skipped = $tester->getLogLines(2000); | ||
|
||
$tester->signal('USR2'); | ||
$tester->expectLogNotice('Reloading in progress ...'); | ||
/* When a child ignores SIGQUIT, the following expectation fails due to timeout. */ | ||
if (!$tester->expectLogNotice('reloading: .*')) { | ||
/* for troubleshooting */ | ||
echo "Skipped messages\n"; | ||
echo implode('', $skipped); | ||
} | ||
$tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"'); | ||
$tester->expectLogStartNotices(); | ||
|
||
$tester->terminate(); | ||
$tester->expectLogTerminatingNotices(); | ||
$tester->close(); | ||
|
||
?> | ||
Done | ||
--EXPECT-- | ||
Reached interval 25000 us with 1000 us steps | ||
Done | ||
--CLEAN-- | ||
<?php | ||
require_once "tester.inc"; | ||
FPM\Tester::clean(); | ||
?> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
few typos (grammar mistakes) in here. Maybe something like: