Skip to content

Commit 7c78375

Browse files
committed
php-fpm: graceful restart without blocking/losing requests
## Issue: When php-fpm gets "graceful" reloaded it will stop accepting requests while finishing reqests it already received. If those requests to finish are of a long running kind like big uploads over a slow connection, this will block new requests to be handled for an unacceptable long time. ### Current behavior ``` | v FPM (OLD) running | v $ sudo kill -USR2 $(cat /run/php-fpm/php-fpm.pid) | v OLD sends SIGQUIT to children v OLD stops handling requests | v OLD keeps running until all children complete or time out | v OLD exec()s v FPM (NEW) starts & re-uses listening socket | v NEW handles incoming requests from now on ``` ## Proposed Solution: In the case of a reloading request, we immediately fork and exec a new fpm instance to seamlessly handle new requests, while the old instance keeps finishing those requests already accepted. ### New behavior ``` | v FPM (OLD) running | v $ sudo kill -USR2 $(cat /run/php-fpm/php-fpm.pid) | v OLD sends SIGQUIT to children v OLD fork()s & exec()s |\ | \ | v FPM (NEW) starts & re-uses listening socket | v NEW handles incoming requests from now on | | v | OLD keeps running until all children complete or time out v | OLD gone - | / / v NEW still going | ``` We've been running this patch since PHP 5.6 (early 2017).
1 parent b31c787 commit 7c78375

6 files changed

+40
-8
lines changed

sapi/fpm/fpm/fpm_process_ctl.c

+20-7
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,16 @@ static void fpm_pctl_exit() /* {{{ */
7777

7878
static void fpm_pctl_exec() /* {{{ */
7979
{
80-
zlog(ZLOG_DEBUG, "Blocking some signals before reexec");
81-
if (0 > fpm_signals_block()) {
82-
zlog(ZLOG_WARNING, "concurrent reloads may be unstable");
80+
switch (fork()) {
81+
case 0:
82+
break;
83+
case -1:
84+
zlog(ZLOG_SYSERROR, "failed to reload: fork() failed");
85+
/* no break */
86+
default:
87+
fpm_global_config.pid_file = NULL;
88+
return;
8389
}
84-
8590
zlog(ZLOG_NOTICE, "reloading: execvp(\"%s\", {\"%s\""
8691
"%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
8792
"%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
@@ -99,6 +104,11 @@ static void fpm_pctl_exec() /* {{{ */
99104
optional_arg(10)
100105
);
101106

107+
zlog(ZLOG_DEBUG, "Blocking some signals before reexec");
108+
if (0 > fpm_signals_block()) {
109+
zlog(ZLOG_WARNING, "concurrent reloads may be unstable");
110+
}
111+
102112
fpm_cleanups_run(FPM_CLEANUP_PARENT_EXEC);
103113
execvp(saved_argv[0], saved_argv);
104114
zlog(ZLOG_SYSERROR, "failed to reload: execvp() failed");
@@ -110,9 +120,8 @@ static void fpm_pctl_action_last() /* {{{ */
110120
{
111121
switch (fpm_state) {
112122
case FPM_PCTL_STATE_RELOADING:
113-
fpm_pctl_exec();
114-
break;
115-
123+
zlog(ZLOG_NOTICE, "exiting after reload");
124+
exit(FPM_EXIT_OK);
116125
case FPM_PCTL_STATE_FINISHING:
117126
case FPM_PCTL_STATE_TERMINATING:
118127
fpm_pctl_exit();
@@ -199,6 +208,10 @@ static void fpm_pctl_action_next() /* {{{ */
199208
fpm_pctl_kill_all(sig);
200209
fpm_signal_sent = sig;
201210
fpm_pctl_timeout_set(timeout);
211+
212+
if (fpm_signal_sent == SIGQUIT && fpm_state == FPM_PCTL_STATE_RELOADING) {
213+
fpm_pctl_exec();
214+
}
202215
}
203216
/* }}} */
204217

sapi/fpm/fpm/fpm_sockets.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,17 @@ static void fpm_sockets_cleanup(int which, void *arg) /* {{{ */
7474

7575
if (which == FPM_CLEANUP_PARENT_EXIT_MAIN) {
7676
if (ls->type == FPM_AF_UNIX) {
77-
unlink(ls->key);
77+
struct sockaddr_un sa_un;
78+
79+
memset(&sa_un, 0, sizeof(sa_un));
80+
strlcpy(sa_un.sun_path, ls->key, sizeof(sa_un.sun_path));
81+
sa_un.sun_family = AF_UNIX;
82+
83+
if (fpm_socket_unix_test_connect(&sa_un, sizeof(sa_un)) == 0) {
84+
zlog(ZLOG_WARNING, "Keeping unix socket, another FPM instance seems to already listen on %s", ls->key);
85+
} else {
86+
unlink(ls->key);
87+
}
7888
}
7989
}
8090
free(ls->key);

sapi/fpm/tests/bug68442-signal-reload.phpt

+2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ $tester->ping('{{ADDR}}');
2929
$tester->signal('USR2');
3030
$tester->expectLogNotice('Reloading in progress ...');
3131
$tester->expectLogNotice('reloading: .*');
32+
$tester->expectLogNotice('exiting after reload');
3233
$tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"');
3334
$tester->expectLogStartNotices();
3435
$tester->ping('{{ADDR}}');
36+
$tester->signal('TERM');
3537
$tester->terminate();
3638
$tester->expectLogTerminatingNotices();
3739
$tester->close();

sapi/fpm/tests/bug74083-concurrent-reload.phpt

+2
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,12 @@ $tester->getLogLines(2000);
5757
$tester->signal('USR2');
5858
$tester->expectLogNotice('Reloading in progress ...');
5959
$tester->expectLogNotice('reloading: .*');
60+
$tester->expectLogNotice('exiting after reload');
6061
$tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"');
6162
$tester->expectLogStartNotices();
6263
$tester->ping('{{ADDR}}');
6364

65+
$tester->signal('TERM');
6466
$tester->terminate();
6567
$tester->expectLogTerminatingNotices();
6668
$tester->close();

sapi/fpm/tests/bug76601-reload-child-signals.phpt

+2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ if (!$tester->expectLogNotice('reloading: .*')) {
7373
echo "Skipped messages\n";
7474
echo implode('', $skipped);
7575
}
76+
$tester->expectLogNotice('exiting after reload');
7677
$tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"');
7778
$tester->expectLogStartNotices();
7879

80+
$tester->signal('TERM');
7981
$tester->terminate();
8082
$tester->expectLogTerminatingNotices();
8183
$tester->close();

sapi/fpm/tests/bug77934-reload-process-control.phpt

+3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@ $tester->ping('{{ADDR}}');
3030
$tester->signal('USR2');
3131
$tester->expectLogNotice('Reloading in progress ...');
3232
$tester->expectLogNotice('reloading: .*');
33+
$tester->expectLogNotice('exiting after reload');
3334
$tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"');
3435
$tester->expectLogStartNotices();
3536
$tester->ping('{{ADDR}}');
37+
38+
$tester->signal('TERM');
3639
$tester->terminate();
3740
$tester->expectLogTerminatingNotices();
3841
$tester->close();

0 commit comments

Comments
 (0)