Skip to content

Commit d8612fb

Browse files
committed
Fix bug #77023: FPM cannot shutdown processes
This change introduces subsequent kill of the process when idle process quit (SIGQUIT) does not succeed. It can happen in some situations and means that FPM is not able to scale down in dynamic pm. Using SIGKILL fixes the issue.
1 parent c854bb2 commit d8612fb

File tree

4 files changed

+84
-5
lines changed

4 files changed

+84
-5
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ PHP NEWS
1313
- FPM:
1414
. Fixed bug #76003 (FPM /status reports wrong number of active processe).
1515
(Jakub Zelenka)
16+
. Fixed bug #77023 (FPM cannot shutdown processes). (Jakub Zelenka)
1617

1718
- MySQLi:
1819
. Fixed bug GH-8267 (MySQLi uses unsupported format specifier on Windows).

sapi/fpm/fpm/fpm_process_ctl.c

+16-4
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ int fpm_pctl_kill(pid_t pid, int how) /* {{{ */
138138
case FPM_PCTL_QUIT :
139139
s = SIGQUIT;
140140
break;
141+
case FPM_PCTL_KILL:
142+
s = SIGKILL;
143+
break;
141144
default :
142145
break;
143146
}
@@ -310,6 +313,17 @@ static void fpm_pctl_check_request_timeout(struct timeval *now) /* {{{ */
310313
}
311314
/* }}} */
312315

316+
static void fpm_pctl_kill_idle_child(struct fpm_child_s *child) /* {{{ */
317+
{
318+
if (child->idle_kill) {
319+
fpm_pctl_kill(child->pid, FPM_PCTL_KILL);
320+
} else {
321+
child->idle_kill = 1;
322+
fpm_pctl_kill(child->pid, FPM_PCTL_QUIT);
323+
}
324+
}
325+
/* }}} */
326+
313327
static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{ */
314328
{
315329
struct fpm_worker_pool_s *wp;
@@ -372,8 +386,7 @@ static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{
372386
fpm_request_last_activity(last_idle_child, &last);
373387
fpm_clock_get(&now);
374388
if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) {
375-
last_idle_child->idle_kill = 1;
376-
fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
389+
fpm_pctl_kill_idle_child(last_idle_child);
377390
}
378391

379392
continue;
@@ -385,8 +398,7 @@ static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{
385398
zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children, %d running children. Spawning rate %d", wp->config->name, active, idle, wp->running_children, wp->idle_spawn_rate);
386399

387400
if (idle > wp->config->pm_max_spare_servers && last_idle_child) {
388-
last_idle_child->idle_kill = 1;
389-
fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
401+
fpm_pctl_kill_idle_child(last_idle_child);
390402
wp->idle_spawn_rate = 1;
391403
continue;
392404
}

sapi/fpm/fpm/fpm_process_ctl.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ enum {
4444
FPM_PCTL_TERM,
4545
FPM_PCTL_STOP,
4646
FPM_PCTL_CONT,
47-
FPM_PCTL_QUIT
47+
FPM_PCTL_QUIT,
48+
FPM_PCTL_KILL
4849
};
4950

5051
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--TEST--
2+
FPM: Blocked SIGQUIT prevents idle process to be killed
3+
--EXTENSIONS--
4+
pcntl
5+
--SKIPIF--
6+
<?php
7+
include "skipif.inc";
8+
if (!function_exists('pcntl_sigprocmask')) die('skip Requires pcntl_sigprocmask()');
9+
if (getenv('SKIP_SLOW_TESTS')) die('skip slow tests excluded by request');
10+
?>
11+
--FILE--
12+
<?php
13+
14+
require_once "tester.inc";
15+
16+
$cfg = <<<EOT
17+
[global]
18+
error_log = {{FILE:LOG}}
19+
pid = {{FILE:PID}}
20+
[unconfined]
21+
listen = {{ADDR}}
22+
pm.status_path = /status
23+
pm = dynamic
24+
pm.max_children = 2
25+
pm.start_servers = 1
26+
pm.min_spare_servers = 1
27+
pm.max_spare_servers = 1
28+
EOT;
29+
30+
$code = <<<EOT
31+
<?php
32+
pcntl_sigprocmask(SIG_BLOCK, [SIGQUIT, SIGTERM]);
33+
usleep(300000);
34+
EOT;
35+
36+
37+
$tester = new FPM\Tester($cfg, $code);
38+
$tester->start();
39+
$tester->expectLogStartNotices();
40+
$tester->multiRequest(2);
41+
$tester->status([
42+
'total processes' => 2,
43+
]);
44+
// wait for process to be killed
45+
sleep(7);
46+
$tester->expectLogWarning('child \\d+ exited on signal 9 \\(SIGKILL\\) after \\d+.\\d+ seconds from start', 'unconfined');
47+
$tester->expectLogNotice('child \\d+ started', 'unconfined');
48+
$tester->expectLogWarning('child \\d+ exited on signal 9 \\(SIGKILL\\) after \\d+.\\d+ seconds from start', 'unconfined');
49+
$tester->expectLogNotice('child \\d+ started', 'unconfined');
50+
$tester->status([
51+
'total processes' => 1,
52+
]);
53+
$tester->terminate();
54+
$tester->expectLogTerminatingNotices();
55+
$tester->close();
56+
57+
?>
58+
Done
59+
--EXPECT--
60+
Done
61+
--CLEAN--
62+
<?php
63+
require_once "tester.inc";
64+
FPM\Tester::clean();
65+
?>

0 commit comments

Comments
 (0)