]> granicus.if.org Git - php/commitdiff
FPM: Add pm.status_listen option
authorJakub Zelenka <bukka@php.net>
Sat, 1 Aug 2020 13:39:33 +0000 (14:39 +0100)
committerJakub Zelenka <bukka@php.net>
Mon, 3 Aug 2020 18:14:13 +0000 (19:14 +0100)
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.

12 files changed:
NEWS
UPGRADING
sapi/fpm/fpm/fpm_children.c
sapi/fpm/fpm/fpm_conf.c
sapi/fpm/fpm/fpm_conf.h
sapi/fpm/fpm/fpm_process_ctl.c
sapi/fpm/fpm/fpm_scoreboard.c
sapi/fpm/fpm/fpm_scoreboard.h
sapi/fpm/fpm/fpm_status.c
sapi/fpm/fpm/fpm_worker_pool.h
sapi/fpm/tests/status-listen.phpt [new file with mode: 0644]
sapi/fpm/www.conf.in

diff --git a/NEWS b/NEWS
index f9f035c52fd936ae56888e0c4b797636882e41cb..61d2cdab44211c17ce5cc0213e9301ea1eca938c 100644 (file)
--- 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)
index dd91049f98068c3f86206dea45bb8fda8b2437ad..36022bbd482c6041967d7052bb40edb7858b9b39 100644 (file)
--- 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.
 
index fd121372f37ce2b8f65baa1d9957b556488f95e2..fe92bdf1b0c0330b69483a11366e9f4ef9126371 100644 (file)
@@ -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);
index cc451c966275367da41de0ab42bd775e55398469..c38f16d1abd398083682fc5befa66c6178e8543a 100644 (file)
@@ -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));
index 42fa3cca2efebab55ac1a0e49c9fe99e53d648d6..cd71bb53fda66a880eaa728072e13022620f99c0 100644 (file)
@@ -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;
index 2bc00178ea88837aaf5b648897bb646b2a2d3455..b8122e2ad32fd8ce10fe9928abc30b5499c0f5ce 100644 (file)
@@ -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) {
index f1ce48a96f5b3cbc8e1ef34078aeb6cbd52f0d65..fe36a9755bba2869506fd85342401156cb307af9 100644 (file)
@@ -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;
 }
index 1fecde1d0feb1d03326723670162410f284cc74d..060eddea982a24723c896651f485b4f044dce2b5 100644 (file)
@@ -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[];
 };
 
index 02f2eae55a86db4685cb1bf0e3de53d35fcf4a53..ecce6e32c4a601f29097f943397d4e0555a43af6 100644 (file)
@@ -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;
index 8f4f440a848471a0218ede1cf996cc00d3cc4a4b..ac596cde60547d10d654b0e0238708f6415a231e 100644 (file)
@@ -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 (file)
index 0000000..c6476c0
--- /dev/null
@@ -0,0 +1,51 @@
+--TEST--
+FPM: Status listen test
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = static
+pm.max_children = 1
+pm.status_listen = {{ADDR[status]}}
+pm.status_path = /status
+EOT;
+
+$expectedStatusData = [
+    'pool'                 => '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--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
index 0bb4008ae1633b0d0b0ecbfd1a5bfeb0d6bdcedb..3d5658a65d133f72718c5474e035dd2d1c7fc6a2 100644 (file)
@@ -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