From: Jakub Zelenka Date: Sat, 1 Aug 2020 13:39:33 +0000 (+0100) Subject: FPM: Add pm.status_listen option X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=44c7128fb726696a7c23ff694d1077cf0cf435d4;p=php 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. --- diff --git a/NEWS b/NEWS index f9f035c52f..61d2cdab44 100644 --- a/NEWS +++ b/NEWS @@ -114,6 +114,7 @@ PHP NEWS - FPM: . Fixed bug #64865 (Search for .user.ini files from script dir up to CONTEXT_DOCUMENT_ROOT). (Will Bender) + . Add pm.status_listen option. (Jakub Zelenka) - GD: . Fixed bug #55005 (imagepolygon num_points requirement). (cmb) diff --git a/UPGRADING b/UPGRADING index dd91049f98..36022bbd48 100644 --- a/UPGRADING +++ b/UPGRADING @@ -712,6 +712,11 @@ PHP 8.0 UPGRADE NOTES . enchant_dict_is_added() . LIBENCHANT_VERSION macro +- FPM: + . Added a new option pm.status_listen that 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. + - Hash: . HashContext objects can now be serialized. diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c index fd121372f3..fe92bdf1b0 100644 --- a/sapi/fpm/fpm/fpm_children.c +++ b/sapi/fpm/fpm/fpm_children.c @@ -346,7 +346,7 @@ static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */ { struct fpm_worker_pool_s *wp; for (wp = fpm_worker_all_pools; wp; wp = wp->next) { - if (wp == child->wp) { + if (wp == child->wp || wp == child->wp->shared) { continue; } fpm_scoreboard_free(wp->scoreboard); diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c index cc451c9662..c38f16d1ab 100644 --- a/sapi/fpm/fpm/fpm_conf.c +++ b/sapi/fpm/fpm/fpm_conf.c @@ -135,6 +135,7 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = { { "pm.process_idle_timeout", &fpm_conf_set_time, WPO(pm_process_idle_timeout) }, { "pm.max_requests", &fpm_conf_set_integer, WPO(pm_max_requests) }, { "pm.status_path", &fpm_conf_set_string, WPO(pm_status_path) }, + { "pm.status_listen", &fpm_conf_set_string, WPO(pm_status_listen) }, { "ping.path", &fpm_conf_set_string, WPO(ping_path) }, { "ping.response", &fpm_conf_set_string, WPO(ping_response) }, { "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) /* {{{ */ } /* }}} */ +#define FPM_WPC_STR_CP_EX(_cfg, _scfg, _sf, _df) \ + do { \ + if (_scfg->_df && !(_cfg->_sf = strdup(_scfg->_df))) { \ + return -1; \ + } \ + } while (0) +#define FPM_WPC_STR_CP(_cfg, _scfg, _field) FPM_WPC_STR_CP_EX(_cfg, _scfg, _field, _field) + +static int fpm_worker_pool_shared_status_alloc(struct fpm_worker_pool_s *shared_wp) { /* {{{ */ + struct fpm_worker_pool_config_s *config, *shared_config; + config = fpm_worker_pool_config_alloc(); + if (!config) { + return -1; + } + shared_config = shared_wp->config; + + config->name = malloc(strlen(shared_config->name) + sizeof("_status")); + if (!config->name) { + return -1; + } + strcpy(config->name, shared_config->name); + strcpy(config->name + strlen(shared_config->name), "_status"); + + if (!shared_config->pm_status_path) { + shared_config->pm_status_path = strdup("/"); + } + + FPM_WPC_STR_CP_EX(config, shared_config, listen_address, pm_status_listen); +#ifdef HAVE_FPM_ACL + FPM_WPC_STR_CP(config, shared_config, listen_acl_groups); + FPM_WPC_STR_CP(config, shared_config, listen_acl_users); +#endif + FPM_WPC_STR_CP(config, shared_config, listen_allowed_clients); + FPM_WPC_STR_CP(config, shared_config, listen_group); + FPM_WPC_STR_CP(config, shared_config, listen_owner); + FPM_WPC_STR_CP(config, shared_config, listen_mode); + FPM_WPC_STR_CP(config, shared_config, user); + FPM_WPC_STR_CP(config, shared_config, group); + FPM_WPC_STR_CP(config, shared_config, pm_status_path); + + config->pm = PM_STYLE_ONDEMAND; + config->pm_max_children = 2; + /* set to 1 to not warn about max children for shared pool */ + shared_wp->warn_max_children = 1; + + current_wp->shared = shared_wp; + + return 0; +} +/* }}} */ + static int fpm_evaluate_full_path(char **path, struct fpm_worker_pool_s *wp, char *default_prefix, int expand) /* {{{ */ { char *prefix = NULL; @@ -856,6 +908,10 @@ static int fpm_conf_process_all_pools() /* {{{ */ } /* status */ + if (wp->config->pm_status_listen && fpm_worker_pool_shared_status_alloc(wp)) { + zlog(ZLOG_ERROR, "[pool %s] failed to initialize a status listener pool", wp->config->name); + } + if (wp->config->pm_status_path && *wp->config->pm_status_path) { size_t i; char *status = wp->config->pm_status_path; @@ -865,7 +921,7 @@ static int fpm_conf_process_all_pools() /* {{{ */ return -1; } - if (strlen(status) < 2) { + if (!wp->config->pm_status_listen && !wp->shared && strlen(status) < 2) { zlog(ZLOG_ERROR, "[pool %s] the status path '%s' is not long enough", wp->config->name, status); return -1; } @@ -1634,7 +1690,11 @@ static void fpm_conf_dump() /* {{{ */ for (wp = fpm_worker_all_pools; wp; wp = wp->next) { struct key_value_s *kv; - if (!wp->config) continue; + + if (!wp->config || wp->shared) { + continue; + } + zlog(ZLOG_NOTICE, "[%s]", STR2STR(wp->config->name)); zlog(ZLOG_NOTICE, "\tprefix = %s", STR2STR(wp->config->prefix)); zlog(ZLOG_NOTICE, "\tuser = %s", STR2STR(wp->config->user)); @@ -1663,6 +1723,7 @@ static void fpm_conf_dump() /* {{{ */ zlog(ZLOG_NOTICE, "\tpm.process_idle_timeout = %d", wp->config->pm_process_idle_timeout); zlog(ZLOG_NOTICE, "\tpm.max_requests = %d", wp->config->pm_max_requests); zlog(ZLOG_NOTICE, "\tpm.status_path = %s", STR2STR(wp->config->pm_status_path)); + zlog(ZLOG_NOTICE, "\tpm.status_listen = %s", STR2STR(wp->config->pm_status_listen)); zlog(ZLOG_NOTICE, "\tping.path = %s", STR2STR(wp->config->ping_path)); zlog(ZLOG_NOTICE, "\tping.response = %s", STR2STR(wp->config->ping_response)); zlog(ZLOG_NOTICE, "\taccess.log = %s", STR2STR(wp->config->access_log)); diff --git a/sapi/fpm/fpm/fpm_conf.h b/sapi/fpm/fpm/fpm_conf.h index 42fa3cca2e..cd71bb53fd 100644 --- a/sapi/fpm/fpm/fpm_conf.h +++ b/sapi/fpm/fpm/fpm_conf.h @@ -73,6 +73,7 @@ struct fpm_worker_pool_config_s { int pm_process_idle_timeout; int pm_max_requests; char *pm_status_path; + char *pm_status_listen; char *ping_path; char *ping_response; char *access_log; diff --git a/sapi/fpm/fpm/fpm_process_ctl.c b/sapi/fpm/fpm/fpm_process_ctl.c index 2bc00178ea..b8122e2ad3 100644 --- a/sapi/fpm/fpm/fpm_process_ctl.c +++ b/sapi/fpm/fpm/fpm_process_ctl.c @@ -417,7 +417,9 @@ static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{ wp->idle_spawn_rate = 1; continue; } - wp->warn_max_children = 0; + if (!wp->shared) { + wp->warn_max_children = 0; + } fpm_children_make(wp, 1, children_to_fork, 1); @@ -528,8 +530,9 @@ void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg) / return; } } - - wp->warn_max_children = 0; + if (!wp->shared) { + wp->warn_max_children = 0; + } fpm_children_make(wp, 1, 1, 1); if (fpm_globals.is_child) { diff --git a/sapi/fpm/fpm/fpm_scoreboard.c b/sapi/fpm/fpm/fpm_scoreboard.c index f1ce48a96f..fe36a9755b 100644 --- a/sapi/fpm/fpm/fpm_scoreboard.c +++ b/sapi/fpm/fpm/fpm_scoreboard.c @@ -71,6 +71,11 @@ int fpm_scoreboard_init_main() /* {{{ */ wp->scoreboard->pm = wp->config->pm; wp->scoreboard->start_epoch = time(NULL); strlcpy(wp->scoreboard->pool, wp->config->name, sizeof(wp->scoreboard->pool)); + + if (wp->shared) { + /* shared pool is added after non shared ones so the shared scoreboard is allocated */ + wp->scoreboard->shared = wp->shared->scoreboard; + } } return 0; } diff --git a/sapi/fpm/fpm/fpm_scoreboard.h b/sapi/fpm/fpm/fpm_scoreboard.h index 1fecde1d0f..060eddea98 100644 --- a/sapi/fpm/fpm/fpm_scoreboard.h +++ b/sapi/fpm/fpm/fpm_scoreboard.h @@ -63,6 +63,7 @@ struct fpm_scoreboard_s { unsigned int nprocs; int free_proc; unsigned long int slow_rq; + struct fpm_scoreboard_s *shared; struct fpm_scoreboard_proc_s *procs[]; }; diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c index 02f2eae55a..ecce6e32c4 100644 --- a/sapi/fpm/fpm/fpm_status.c +++ b/sapi/fpm/fpm/fpm_status.c @@ -171,6 +171,9 @@ int fpm_status_handle_request(void) /* {{{ */ fpm_request_executing(); scoreboard_p = fpm_scoreboard_get(); + if (scoreboard_p->shared) { + scoreboard_p = scoreboard_p->shared; + } if (!scoreboard_p) { zlog(ZLOG_ERROR, "status: unable to find or access status shared memory"); SG(sapi_headers).http_response_code = 500; diff --git a/sapi/fpm/fpm/fpm_worker_pool.h b/sapi/fpm/fpm/fpm_worker_pool.h index 8f4f440a84..ac596cde60 100644 --- a/sapi/fpm/fpm/fpm_worker_pool.h +++ b/sapi/fpm/fpm/fpm_worker_pool.h @@ -18,6 +18,7 @@ enum fpm_address_domain { struct fpm_worker_pool_s { struct fpm_worker_pool_s *next; + struct fpm_worker_pool_s *shared; struct fpm_worker_pool_config_s *config; char *user, *home; /* for setting env USER and HOME */ enum fpm_address_domain listen_address_domain; diff --git a/sapi/fpm/tests/status-listen.phpt b/sapi/fpm/tests/status-listen.phpt new file mode 100644 index 0000000000..c6476c0332 --- /dev/null +++ b/sapi/fpm/tests/status-listen.phpt @@ -0,0 +1,51 @@ +--TEST-- +FPM: Status listen test +--SKIPIF-- + +--FILE-- + 'unconfined', + 'process manager' => 'static', + 'listen queue' => 0, + 'max listen queue' => 0, + 'idle processes' => 1, + 'active processes' => 0, + 'total processes' => 1, + 'max active processes' => 1, + 'max children reached' => 0, + 'slow requests' => 0, +]; + +$tester = new FPM\Tester($cfg); +$tester->start(); +$tester->expectLogStartNotices(); +$tester->request()->expectEmptyBody(); +$tester->status($expectedStatusData, '{{ADDR[status]}}'); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/www.conf.in b/sapi/fpm/www.conf.in index 0bb4008ae1..3d5658a65d 100644 --- a/sapi/fpm/www.conf.in +++ b/sapi/fpm/www.conf.in @@ -238,6 +238,22 @@ pm.max_spare_servers = 3 ; Default Value: not set ;pm.status_path = /status +; The address on which to accept FastCGI status request. This creates a new +; invisible pool that can handle requests independently. This is useful +; if the main pool is busy with long running requests because it is still possible +; to get the status before finishing the long running requests. +; +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on +; a specific port; +; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses +; (IPv6 and IPv4-mapped) on a specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Default Value: value of the listen option +;pm.status_listen = 127.0.0.1:9001 + ; The ping URI to call the monitoring page of FPM. If this value is not set, no ; URI will be recognized as a ping page. This could be used to test from outside ; that FPM is alive and responding, or to