Skip to content

Commit 44c7128

Browse files
committed
FPM: Add pm.status_listen option
This option allows getting status from different endpoint (e.g. port or UDS file) which is useful for getting status when all children are busy with serving long running requests. Internally a new shared pool with ondemand process manager is used. It means that the status requests have reserved resources and should not be blocked by other requests.
1 parent 4c89ed6 commit 44c7128

12 files changed

+154
-6
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ PHP NEWS
114114
- FPM:
115115
. Fixed bug #64865 (Search for .user.ini files from script dir up to
116116
CONTEXT_DOCUMENT_ROOT). (Will Bender)
117+
. Add pm.status_listen option. (Jakub Zelenka)
117118

118119
- GD:
119120
. Fixed bug #55005 (imagepolygon num_points requirement). (cmb)

UPGRADING

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,11 @@ PHP 8.0 UPGRADE NOTES
712712
. enchant_dict_is_added()
713713
. LIBENCHANT_VERSION macro
714714

715+
- FPM:
716+
. Added a new option pm.status_listen that allows getting status from
717+
different endpoint (e.g. port or UDS file) which is useful for getting
718+
status when all children are busy with serving long running requests.
719+
715720
- Hash:
716721
. HashContext objects can now be serialized.
717722

sapi/fpm/fpm/fpm_children.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */
346346
{
347347
struct fpm_worker_pool_s *wp;
348348
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
349-
if (wp == child->wp) {
349+
if (wp == child->wp || wp == child->wp->shared) {
350350
continue;
351351
}
352352
fpm_scoreboard_free(wp->scoreboard);

sapi/fpm/fpm/fpm_conf.c

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = {
135135
{ "pm.process_idle_timeout", &fpm_conf_set_time, WPO(pm_process_idle_timeout) },
136136
{ "pm.max_requests", &fpm_conf_set_integer, WPO(pm_max_requests) },
137137
{ "pm.status_path", &fpm_conf_set_string, WPO(pm_status_path) },
138+
{ "pm.status_listen", &fpm_conf_set_string, WPO(pm_status_listen) },
138139
{ "ping.path", &fpm_conf_set_string, WPO(ping_path) },
139140
{ "ping.response", &fpm_conf_set_string, WPO(ping_response) },
140141
{ "access.log", &fpm_conf_set_string, WPO(access_log) },
@@ -682,6 +683,57 @@ int fpm_worker_pool_config_free(struct fpm_worker_pool_config_s *wpc) /* {{{ */
682683
}
683684
/* }}} */
684685

686+
#define FPM_WPC_STR_CP_EX(_cfg, _scfg, _sf, _df) \
687+
do { \
688+
if (_scfg->_df && !(_cfg->_sf = strdup(_scfg->_df))) { \
689+
return -1; \
690+
} \
691+
} while (0)
692+
#define FPM_WPC_STR_CP(_cfg, _scfg, _field) FPM_WPC_STR_CP_EX(_cfg, _scfg, _field, _field)
693+
694+
static int fpm_worker_pool_shared_status_alloc(struct fpm_worker_pool_s *shared_wp) { /* {{{ */
695+
struct fpm_worker_pool_config_s *config, *shared_config;
696+
config = fpm_worker_pool_config_alloc();
697+
if (!config) {
698+
return -1;
699+
}
700+
shared_config = shared_wp->config;
701+
702+
config->name = malloc(strlen(shared_config->name) + sizeof("_status"));
703+
if (!config->name) {
704+
return -1;
705+
}
706+
strcpy(config->name, shared_config->name);
707+
strcpy(config->name + strlen(shared_config->name), "_status");
708+
709+
if (!shared_config->pm_status_path) {
710+
shared_config->pm_status_path = strdup("/");
711+
}
712+
713+
FPM_WPC_STR_CP_EX(config, shared_config, listen_address, pm_status_listen);
714+
#ifdef HAVE_FPM_ACL
715+
FPM_WPC_STR_CP(config, shared_config, listen_acl_groups);
716+
FPM_WPC_STR_CP(config, shared_config, listen_acl_users);
717+
#endif
718+
FPM_WPC_STR_CP(config, shared_config, listen_allowed_clients);
719+
FPM_WPC_STR_CP(config, shared_config, listen_group);
720+
FPM_WPC_STR_CP(config, shared_config, listen_owner);
721+
FPM_WPC_STR_CP(config, shared_config, listen_mode);
722+
FPM_WPC_STR_CP(config, shared_config, user);
723+
FPM_WPC_STR_CP(config, shared_config, group);
724+
FPM_WPC_STR_CP(config, shared_config, pm_status_path);
725+
726+
config->pm = PM_STYLE_ONDEMAND;
727+
config->pm_max_children = 2;
728+
/* set to 1 to not warn about max children for shared pool */
729+
shared_wp->warn_max_children = 1;
730+
731+
current_wp->shared = shared_wp;
732+
733+
return 0;
734+
}
735+
/* }}} */
736+
685737
static int fpm_evaluate_full_path(char **path, struct fpm_worker_pool_s *wp, char *default_prefix, int expand) /* {{{ */
686738
{
687739
char *prefix = NULL;
@@ -856,6 +908,10 @@ static int fpm_conf_process_all_pools() /* {{{ */
856908
}
857909

858910
/* status */
911+
if (wp->config->pm_status_listen && fpm_worker_pool_shared_status_alloc(wp)) {
912+
zlog(ZLOG_ERROR, "[pool %s] failed to initialize a status listener pool", wp->config->name);
913+
}
914+
859915
if (wp->config->pm_status_path && *wp->config->pm_status_path) {
860916
size_t i;
861917
char *status = wp->config->pm_status_path;
@@ -865,7 +921,7 @@ static int fpm_conf_process_all_pools() /* {{{ */
865921
return -1;
866922
}
867923

868-
if (strlen(status) < 2) {
924+
if (!wp->config->pm_status_listen && !wp->shared && strlen(status) < 2) {
869925
zlog(ZLOG_ERROR, "[pool %s] the status path '%s' is not long enough", wp->config->name, status);
870926
return -1;
871927
}
@@ -1634,7 +1690,11 @@ static void fpm_conf_dump() /* {{{ */
16341690

16351691
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
16361692
struct key_value_s *kv;
1637-
if (!wp->config) continue;
1693+
1694+
if (!wp->config || wp->shared) {
1695+
continue;
1696+
}
1697+
16381698
zlog(ZLOG_NOTICE, "[%s]", STR2STR(wp->config->name));
16391699
zlog(ZLOG_NOTICE, "\tprefix = %s", STR2STR(wp->config->prefix));
16401700
zlog(ZLOG_NOTICE, "\tuser = %s", STR2STR(wp->config->user));
@@ -1663,6 +1723,7 @@ static void fpm_conf_dump() /* {{{ */
16631723
zlog(ZLOG_NOTICE, "\tpm.process_idle_timeout = %d", wp->config->pm_process_idle_timeout);
16641724
zlog(ZLOG_NOTICE, "\tpm.max_requests = %d", wp->config->pm_max_requests);
16651725
zlog(ZLOG_NOTICE, "\tpm.status_path = %s", STR2STR(wp->config->pm_status_path));
1726+
zlog(ZLOG_NOTICE, "\tpm.status_listen = %s", STR2STR(wp->config->pm_status_listen));
16661727
zlog(ZLOG_NOTICE, "\tping.path = %s", STR2STR(wp->config->ping_path));
16671728
zlog(ZLOG_NOTICE, "\tping.response = %s", STR2STR(wp->config->ping_response));
16681729
zlog(ZLOG_NOTICE, "\taccess.log = %s", STR2STR(wp->config->access_log));

sapi/fpm/fpm/fpm_conf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ struct fpm_worker_pool_config_s {
7373
int pm_process_idle_timeout;
7474
int pm_max_requests;
7575
char *pm_status_path;
76+
char *pm_status_listen;
7677
char *ping_path;
7778
char *ping_response;
7879
char *access_log;

sapi/fpm/fpm/fpm_process_ctl.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,9 @@ static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{
417417
wp->idle_spawn_rate = 1;
418418
continue;
419419
}
420-
wp->warn_max_children = 0;
420+
if (!wp->shared) {
421+
wp->warn_max_children = 0;
422+
}
421423

422424
fpm_children_make(wp, 1, children_to_fork, 1);
423425

@@ -528,8 +530,9 @@ void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg) /
528530
return;
529531
}
530532
}
531-
532-
wp->warn_max_children = 0;
533+
if (!wp->shared) {
534+
wp->warn_max_children = 0;
535+
}
533536
fpm_children_make(wp, 1, 1, 1);
534537

535538
if (fpm_globals.is_child) {

sapi/fpm/fpm/fpm_scoreboard.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ int fpm_scoreboard_init_main() /* {{{ */
7171
wp->scoreboard->pm = wp->config->pm;
7272
wp->scoreboard->start_epoch = time(NULL);
7373
strlcpy(wp->scoreboard->pool, wp->config->name, sizeof(wp->scoreboard->pool));
74+
75+
if (wp->shared) {
76+
/* shared pool is added after non shared ones so the shared scoreboard is allocated */
77+
wp->scoreboard->shared = wp->shared->scoreboard;
78+
}
7479
}
7580
return 0;
7681
}

sapi/fpm/fpm/fpm_scoreboard.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct fpm_scoreboard_s {
6363
unsigned int nprocs;
6464
int free_proc;
6565
unsigned long int slow_rq;
66+
struct fpm_scoreboard_s *shared;
6667
struct fpm_scoreboard_proc_s *procs[];
6768
};
6869

sapi/fpm/fpm/fpm_status.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ int fpm_status_handle_request(void) /* {{{ */
171171
fpm_request_executing();
172172

173173
scoreboard_p = fpm_scoreboard_get();
174+
if (scoreboard_p->shared) {
175+
scoreboard_p = scoreboard_p->shared;
176+
}
174177
if (!scoreboard_p) {
175178
zlog(ZLOG_ERROR, "status: unable to find or access status shared memory");
176179
SG(sapi_headers).http_response_code = 500;

sapi/fpm/fpm/fpm_worker_pool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ enum fpm_address_domain {
1818

1919
struct fpm_worker_pool_s {
2020
struct fpm_worker_pool_s *next;
21+
struct fpm_worker_pool_s *shared;
2122
struct fpm_worker_pool_config_s *config;
2223
char *user, *home; /* for setting env USER and HOME */
2324
enum fpm_address_domain listen_address_domain;

sapi/fpm/tests/status-listen.phpt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--TEST--
2+
FPM: Status listen test
3+
--SKIPIF--
4+
<?php include "skipif.inc"; ?>
5+
--FILE--
6+
<?php
7+
8+
require_once "tester.inc";
9+
10+
$cfg = <<<EOT
11+
[global]
12+
error_log = {{FILE:LOG}}
13+
[unconfined]
14+
listen = {{ADDR}}
15+
pm = static
16+
pm.max_children = 1
17+
pm.status_listen = {{ADDR[status]}}
18+
pm.status_path = /status
19+
EOT;
20+
21+
$expectedStatusData = [
22+
'pool' => 'unconfined',
23+
'process manager' => 'static',
24+
'listen queue' => 0,
25+
'max listen queue' => 0,
26+
'idle processes' => 1,
27+
'active processes' => 0,
28+
'total processes' => 1,
29+
'max active processes' => 1,
30+
'max children reached' => 0,
31+
'slow requests' => 0,
32+
];
33+
34+
$tester = new FPM\Tester($cfg);
35+
$tester->start();
36+
$tester->expectLogStartNotices();
37+
$tester->request()->expectEmptyBody();
38+
$tester->status($expectedStatusData, '{{ADDR[status]}}');
39+
$tester->terminate();
40+
$tester->expectLogTerminatingNotices();
41+
$tester->close();
42+
43+
?>
44+
Done
45+
--EXPECT--
46+
Done
47+
--CLEAN--
48+
<?php
49+
require_once "tester.inc";
50+
FPM\Tester::clean();
51+
?>

sapi/fpm/www.conf.in

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,22 @@ pm.max_spare_servers = 3
238238
; Default Value: not set
239239
;pm.status_path = /status
240240

241+
; The address on which to accept FastCGI status request. This creates a new
242+
; invisible pool that can handle requests independently. This is useful
243+
; if the main pool is busy with long running requests because it is still possible
244+
; to get the status before finishing the long running requests.
245+
;
246+
; Valid syntaxes are:
247+
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
248+
; a specific port;
249+
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
250+
; a specific port;
251+
; 'port' - to listen on a TCP socket to all addresses
252+
; (IPv6 and IPv4-mapped) on a specific port;
253+
; '/path/to/unix/socket' - to listen on a unix socket.
254+
; Default Value: value of the listen option
255+
;pm.status_listen = 127.0.0.1:9001
256+
241257
; The ping URI to call the monitoring page of FPM. If this value is not set, no
242258
; URI will be recognized as a ping page. This could be used to test from outside
243259
; that FPM is alive and responding, or to

0 commit comments

Comments
 (0)