Hi all, I spent some time working on the missing apache_like feature in FPM.
The attached patch is the first version and is for testing. Changes: 1- rename apache_like to dynamic (in conf file and in source file) 2- add the dynamic spawning algorithm The algorithm is based on the mod_worker from apache httpd Algorihtm: check start_servers, min_spare_servers, max_spare_servers if start_server is not set, set it to (min_spare + (max_spare - min_spare) / 2) for each pool, set a variable spawn_rate to 1 set a 1s timer event to perform idle server maintenance each second for each pool { calculate nb_idle, number of idle process (based on child->time_accepted which is set when a child accept() call returns and reset when it call accept()) if idle > max_spare { kill the oldest idle child set spawn_rate to 1 continue to next pool } if idle < min_spare { set nb_children to MIN(spawn_rate, (min_spare - nb_idle)) create nb_children if (spawn_rate < 32) { spawn_rate * 2 } continue to next next pool } // nothing has been done (no fork and no kill) reset spawn_rate to 1 continue to next pool } spawn_rate is used to increase the number of child to create when there is a lot of children to create at once. The first iteration it will create 1, then 2, then 4, then 8, then 16. I didn't do anything about handling the gracefull restart. I'll check that later. You can test it and make me returns. Hope it helps ++ Jerome
Index: sapi/fpm/fpm/fpm_request.h =================================================================== --- sapi/fpm/fpm/fpm_request.h (révision 292067) +++ sapi/fpm/fpm/fpm_request.h (copie de travail) @@ -15,6 +15,7 @@ struct timeval; void fpm_request_check_timed_out(struct fpm_child_s *child, struct timeval *tv, int terminate_timeout, int slowlog_timeout); +int fpm_request_is_idle(struct fpm_child_s *child); enum fpm_request_stage_e { FPM_REQUEST_ACCEPTING = 1, Index: sapi/fpm/fpm/fpm_children.h =================================================================== --- sapi/fpm/fpm/fpm_children.h (révision 292067) +++ sapi/fpm/fpm/fpm_children.h (copie de travail) @@ -15,6 +15,7 @@ int fpm_children_free(struct fpm_child_s *child); void fpm_children_bury(); int fpm_children_init_main(); +int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn); struct fpm_child_s; @@ -27,6 +28,7 @@ int fd_stdout, fd_stderr; void (*tracer)(struct fpm_child_s *); struct timeval slow_logged; + int idle_kill; pid_t pid; }; Index: sapi/fpm/fpm/fpm_config.h =================================================================== --- sapi/fpm/fpm/fpm_config.h (révision 292067) +++ sapi/fpm/fpm/fpm_config.h (copie de travail) @@ -32,6 +32,10 @@ } while (0) #endif +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + #if defined(HAVE_PTRACE) || defined(PROC_MEM_FILE) || defined(HAVE_MACH_VM_READ) #define HAVE_FPM_TRACE 1 #else Index: sapi/fpm/fpm/fpm_process_ctl.c =================================================================== --- sapi/fpm/fpm/fpm_process_ctl.c (révision 292067) +++ sapi/fpm/fpm/fpm_process_ctl.c (copie de travail) @@ -307,10 +307,95 @@ } } } - } /* }}} */ +static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + struct fpm_child_s *last_idle_child = NULL; + int i; + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + int terminate_timeout = wp->config->request_terminate_timeout; + int slowlog_timeout = wp->config->request_slowlog_timeout; + struct fpm_child_s *child; + int idle = 0; + int active = 0; + struct timeval tv; + + if (wp->config == NULL) continue; + if (wp->config->pm->style != PM_STYLE_DYNAMIC) continue; + + for (child = wp->children; child; child = child->next) { + int ret = fpm_request_is_idle(child); + if (ret == 1) { + if (last_idle_child == NULL) { + last_idle_child = child; + } else { + if (child->started.tv_sec < last_idle_child->started.tv_sec) { + last_idle_child = child; + } + } + idle++; + } else if (ret == 0) { + active++; + } + } + + zlog(ZLOG_STUFF, ZLOG_DEBUG, "[%s] rate=%d idle=%d active=%d total=%d", wp->config->name, wp->idle_spawn_rate, idle, active, wp->running_children); + + if ((active + idle) != wp->running_children) { + zlog(ZLOG_STUFF, ZLOG_ERROR, "[%s] unable to retrieve spawning informations", wp->config->name); + continue; + } + + if (idle > wp->config->pm->dynamic.max_spare_servers && last_idle_child) { + last_idle_child->idle_kill = 1; + fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_TERM); + wp->idle_spawn_rate = 1; + continue; + } + + if (idle < wp->config->pm->dynamic.min_spare_servers) { + if (wp->running_children >= wp->config->pm->max_children) { + if (!wp->warn_max_children) { + zlog(ZLOG_STUFF, ZLOG_WARNING, "pool %s: server reached max_children setting, consider raising it", + wp->config->name); + wp->warn_max_children = 1; + } + wp->idle_spawn_rate = 1; + continue; + } + wp->warn_max_children = 0; + + if (wp->idle_spawn_rate >= 8) { + zlog(ZLOG_STUFF, ZLOG_WARNING, "pool %s seems busy (you may need to increase start_servers, or min/max_spare_servers), spawning %d children, there are %d idle, and %d total children", wp->config->name, wp->idle_spawn_rate, idle, wp->running_children); + } + + i = MIN(wp->idle_spawn_rate, wp->config->pm->dynamic.min_spare_servers - idle); + fpm_children_make(wp, 1, i); + + /* if it's a child, stop here without creating the next event + * this event is reserved to the master process + */ + if (fpm_globals.is_child) { + return; + } + + zlog(ZLOG_STUFF, ZLOG_NOTICE, "pool %s: %d child(ren) have been created because of not enough spare children", wp->config->name, i); + + /* Double the spawn rate for the next iteration */ + if (wp->idle_spawn_rate < FPM_MAX_SPAWN_RATE) { + wp->idle_spawn_rate *= 2; + } + continue; + } + wp->idle_spawn_rate = 1; + } +} +/* }}} */ + void fpm_pctl_heartbeat(int fd, short which, void *arg) /* {{{ */ { static struct event heartbeat; @@ -328,3 +413,29 @@ } /* }}} */ +void fpm_pctl_perform_idle_server_maintenance_heartbeat(int fd, short which, void *arg) /* {{{ */ +{ + static struct event heartbeat; + struct timeval tv = { .tv_sec = 0, .tv_usec = FPM_IDLE_SERVER_MAINTENANCE_HEARTBEAT }; + struct timeval now; + + if (which == EV_TIMEOUT) { + evtimer_del(&heartbeat); + fpm_clock_get(&now); + if (fpm_pctl_can_spawn_children()) { + fpm_pctl_perform_idle_server_maintenance(&now); + + /* if it's a child, stop here without creating the next event + * this event is reserved to the master process + */ + if (fpm_globals.is_child) { + return; + } + } + } + + evtimer_set(&heartbeat, &fpm_pctl_perform_idle_server_maintenance_heartbeat, 0); + evtimer_add(&heartbeat, &tv); +} +/* }}} */ + Index: sapi/fpm/fpm/fpm_conf.c =================================================================== --- sapi/fpm/fpm/fpm_conf.c (révision 292067) +++ sapi/fpm/fpm/fpm_conf.c (copie de travail) @@ -82,8 +82,8 @@ if (!strcmp(value, "static")) { c->style = PM_STYLE_STATIC; - } else if (!strcmp(value, "apache-like")) { - c->style = PM_STYLE_APACHE_LIKE; + } else if (!strcmp(value, "dynamic")) { + c->style = PM_STYLE_DYNAMIC; } else { return "invalid value for 'style'"; } @@ -139,19 +139,19 @@ } /* }}} */ -static struct xml_conf_section fpm_conf_set_apache_like_subsection_conf = { - .path = "apache_like somewhere", /* fixme */ +static struct xml_conf_section fpm_conf_set_dynamic_subsection_conf = { + .path = "dynamic somewhere", /* fixme */ .parsers = (struct xml_value_parser []) { - { XML_CONF_SCALAR, "StartServers", &xml_conf_set_slot_integer, offsetof(struct fpm_pm_s, options_apache_like.StartServers) }, - { XML_CONF_SCALAR, "MinSpareServers", &xml_conf_set_slot_integer, offsetof(struct fpm_pm_s, options_apache_like.MinSpareServers) }, - { XML_CONF_SCALAR, "MaxSpareServers", &xml_conf_set_slot_integer, offsetof(struct fpm_pm_s, options_apache_like.MaxSpareServers) }, + { XML_CONF_SCALAR, "start_servers", &xml_conf_set_slot_integer, offsetof(struct fpm_pm_s, dynamic.start_servers) }, + { XML_CONF_SCALAR, "min_spare_servers", &xml_conf_set_slot_integer, offsetof(struct fpm_pm_s, dynamic.min_spare_servers) }, + { XML_CONF_SCALAR, "max_spare_servers", &xml_conf_set_slot_integer, offsetof(struct fpm_pm_s, dynamic.max_spare_servers) }, { 0, 0, 0, 0 } } }; -static char *fpm_conf_set_apache_like_subsection(void **conf, char *name, void *xml_node, intptr_t offset) /* {{{ */ +static char *fpm_conf_set_dynamic_subsection(void **conf, char *name, void *xml_node, intptr_t offset) /* {{{ */ { - return xml_conf_parse_section(conf, &fpm_conf_set_apache_like_subsection_conf, xml_node); + return xml_conf_parse_section(conf, &fpm_conf_set_dynamic_subsection_conf, xml_node); } /* }}} */ @@ -191,7 +191,7 @@ .parsers = (struct xml_value_parser []) { { XML_CONF_SCALAR, "style", &fpm_conf_set_pm_style, 0 }, { XML_CONF_SCALAR, "max_children", &xml_conf_set_slot_integer, offsetof(struct fpm_pm_s, max_children) }, - { XML_CONF_SUBSECTION, "apache_like", &fpm_conf_set_apache_like_subsection, offsetof(struct fpm_pm_s, options_apache_like) }, + { XML_CONF_SUBSECTION, "dynamic", &fpm_conf_set_dynamic_subsection, offsetof(struct fpm_pm_s, dynamic) }, { 0, 0, 0, 0 } } }; @@ -392,6 +392,46 @@ wp->is_template = 1; } + if (wp->config->pm == NULL) { + zlog(ZLOG_STUFF, ZLOG_ALERT, "pool %s: the process manager is missing (static or dynamic)", wp->config->name); + return(-1); + } + + if (wp->config->pm->style == PM_STYLE_DYNAMIC) { + struct fpm_pm_s *pm = wp->config->pm; + + if (pm->dynamic.min_spare_servers <= 0) { + zlog(ZLOG_STUFF, ZLOG_ALERT, "pool %s: min_spare_servers must be a positive value", wp->config->name); + return(-1); + } + + if (pm->dynamic.max_spare_servers <= 0) { + zlog(ZLOG_STUFF, ZLOG_ALERT, "pool %s: max_spare_servers must be a positive value", wp->config->name); + return(-1); + } + + if (pm->dynamic.min_spare_servers > pm->max_children || + pm->dynamic.max_spare_servers > pm->max_children) { + zlog(ZLOG_STUFF, ZLOG_ALERT, "pool %s: min_spare_servers(%d) and max_spare_servers(%d) can't be greater than max_children(%d)", + wp->config->name, pm->dynamic.min_spare_servers, pm->dynamic.max_spare_servers, pm->max_children); + return(-1); + } + + if (pm->dynamic.max_spare_servers < pm->dynamic.min_spare_servers) { + zlog(ZLOG_STUFF, ZLOG_ALERT, "pool %s: max_spare_servers must be greater or equal than min_spare_servers", wp->config->name); + return(-1); + } + + if (pm->dynamic.start_servers <= 0) { + pm->dynamic.start_servers = pm->dynamic.min_spare_servers + ((pm->dynamic.max_spare_servers - pm->dynamic.min_spare_servers) / 2); + zlog(ZLOG_STUFF, ZLOG_NOTICE, "pool %s: start_servers has been set to %d", wp->config->name, pm->dynamic.start_servers); + } else if (pm->dynamic.start_servers < pm->dynamic.min_spare_servers || pm->dynamic.start_servers > pm->dynamic.max_spare_servers) { + zlog(ZLOG_STUFF, ZLOG_ALERT, "pool %s: start_servers must not be less than min_spare_servers and not greaters than max_spare_servers", wp->config->name); + return(-1); + } + } + + if (wp->config->request_slowlog_timeout) { #if HAVE_FPM_TRACE if (! (wp->config->slowlog && *wp->config->slowlog)) { Index: sapi/fpm/fpm/fpm_process_ctl.h =================================================================== --- sapi/fpm/fpm/fpm_process_ctl.h (révision 292067) +++ sapi/fpm/fpm/fpm_process_ctl.h (copie de travail) @@ -5,12 +5,18 @@ #ifndef FPM_PROCESS_CTL_H #define FPM_PROCESS_CTL_H 1 +/* spawn max 32 children at once */ +#define FPM_MAX_SPAWN_RATE (32) +/* 1s (in µs here) heatbeat for idle server maintenance */ +#define FPM_IDLE_SERVER_MAINTENANCE_HEARTBEAT (1000000) + struct fpm_child_s; void fpm_pctl(int new_state, int action); int fpm_pctl_can_spawn_children(); int fpm_pctl_kill(pid_t pid, int how); void fpm_pctl_heartbeat(int fd, short which, void *arg); +void fpm_pctl_perform_idle_server_maintenance_heartbeat(int fd, short which, void *arg); int fpm_pctl_child_exited(); int fpm_pctl_init_main(); Index: sapi/fpm/fpm/fpm_conf.h =================================================================== --- sapi/fpm/fpm/fpm_conf.h (révision 292067) +++ sapi/fpm/fpm/fpm_conf.h (copie de travail) @@ -28,10 +28,10 @@ int style; int max_children; struct { - int StartServers; - int MinSpareServers; - int MaxSpareServers; - } options_apache_like; + int start_servers; + int min_spare_servers; + int max_spare_servers; + } dynamic; }; struct fpm_listen_options_s { @@ -62,7 +62,7 @@ unsigned catch_workers_output:1; }; -enum { PM_STYLE_STATIC = 1, PM_STYLE_APACHE_LIKE = 2 }; +enum { PM_STYLE_STATIC = 1, PM_STYLE_DYNAMIC = 2 }; int fpm_conf_init_main(); int fpm_worker_pool_config_free(struct fpm_worker_pool_config_s *wpc); Index: sapi/fpm/fpm/fpm_worker_pool.c =================================================================== --- sapi/fpm/fpm/fpm_worker_pool.c (révision 292067) +++ sapi/fpm/fpm/fpm_worker_pool.c (copie de travail) @@ -53,6 +53,7 @@ fpm_array_init(&ret->slots_used, sizeof(struct fpm_shm_slot_ptr_s), 50); fpm_array_init(&ret->slots_free, sizeof(struct fpm_shm_slot_ptr_s), 50); + ret->idle_spawn_rate = 1; return ret; } /* }}} */ Index: sapi/fpm/fpm/fpm_events.c =================================================================== --- sapi/fpm/fpm/fpm_events.c (révision 292067) +++ sapi/fpm/fpm/fpm_events.c (copie de travail) @@ -100,6 +100,7 @@ event_set(&signal_fd_event, fpm_signals_get_fd(), EV_PERSIST | EV_READ, &fpm_got_signal, 0); event_add(&signal_fd_event, 0); fpm_pctl_heartbeat(-1, 0, 0); + fpm_pctl_perform_idle_server_maintenance_heartbeat(-1, 0, 0); zlog(ZLOG_STUFF, ZLOG_NOTICE, "libevent: entering main loop"); event_loop(0); return 0; Index: sapi/fpm/fpm/fpm_worker_pool.h =================================================================== --- sapi/fpm/fpm/fpm_worker_pool.h (révision 292067) +++ sapi/fpm/fpm/fpm_worker_pool.h (copie de travail) @@ -35,6 +35,8 @@ /* runtime */ struct fpm_child_s *children; int running_children; + int idle_spawn_rate; + int warn_max_children; }; struct fpm_worker_pool_s *fpm_worker_pool_alloc(); Index: sapi/fpm/fpm/fastcgi.c =================================================================== --- sapi/fpm/fpm/fastcgi.c (révision 292067) +++ sapi/fpm/fpm/fastcgi.c (copie de travail) @@ -986,13 +986,13 @@ int n = 0; int allowed = 0; - while (allowed_clients[n] != INADDR_NONE) { - if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) { - allowed = 1; - break; - } - n++; + while (allowed_clients[n] != INADDR_NONE) { + if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) { + allowed = 1; + break; } + n++; + } if (!allowed) { fprintf(stderr, "Connection from disallowed IP address '%s' is dropped.\n", inet_ntoa(sa.sa_inet.sin_addr)); closesocket(req->fd); Index: sapi/fpm/fpm/fpm_request.c =================================================================== --- sapi/fpm/fpm/fpm_request.c (révision 292067) +++ sapi/fpm/fpm/fpm_request.c (copie de travail) @@ -148,3 +148,18 @@ } /* }}} */ +int fpm_request_is_idle(struct fpm_child_s *child) /* {{{ */ +{ + struct fpm_shm_slot_s *slot; + struct fpm_shm_slot_s slot_c; + + slot = fpm_shm_slot(child); + if (!fpm_shm_slots_acquire(slot, 1)) { + return(-1); + } + + slot_c = *slot; + fpm_shm_slots_release(slot); + return(!slot_c.accepted.tv_sec && !slot_c.accepted.tv_usec ? 1 : 0); +} +/* }}} */ Index: sapi/fpm/fpm/fpm_children.c =================================================================== --- sapi/fpm/fpm/fpm_children.c (révision 292067) +++ sapi/fpm/fpm/fpm_children.c (copie de travail) @@ -32,8 +32,6 @@ static time_t *last_faults; static int fault; -static int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop); - static void fpm_children_cleanup(int which, void *arg) /* {{{ */ { free(last_faults); @@ -180,6 +178,7 @@ while ( (pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { char buf[128]; int severity = ZLOG_NOTICE; + int restart_child = 1; child = fpm_child_find(pid); @@ -187,6 +186,13 @@ snprintf(buf, sizeof(buf), "with code %d", WEXITSTATUS(status)); + /* if it's been killed because of dynamic process management + * don't restart it automaticaly + */ + if (child && child->idle_kill) { + restart_child = 0; + } + if (WEXITSTATUS(status) != 0) { severity = ZLOG_WARNING; } @@ -201,6 +207,13 @@ snprintf(buf, sizeof(buf), "on signal %d %s%s", WTERMSIG(status), signame, have_core); + /* if it's been killed because of dynamic process management + * don't restart it automaticaly + */ + if (child && child->idle_kill && WTERMSIG(status) == SIGTERM) { + restart_child = 0; + } + if (WTERMSIG(status) != SIGQUIT) { /* possible request loss */ severity = ZLOG_WARNING; } @@ -227,8 +240,13 @@ timersub(&tv1, &child->started, &tv2); - zlog(ZLOG_STUFF, severity, "child %d (pool %s) exited %s after %ld.%06d seconds from start", (int) pid, - child->wp->config->name, buf, tv2.tv_sec, (int) tv2.tv_usec); + if (restart_child) { + zlog(ZLOG_STUFF, severity, "child %d (pool %s) exited %s after %ld.%06d seconds from start", (int) pid, + child->wp->config->name, buf, tv2.tv_sec, (int) tv2.tv_usec); + } else { + zlog(ZLOG_STUFF, severity, "child %d (pool %s) has been killed by the process managment after %ld.%06d seconds from start", (int) pid, + child->wp->config->name, tv2.tv_sec, (int) tv2.tv_usec); + } fpm_child_close(child, 1 /* in event_loop */); @@ -261,11 +279,13 @@ } } - fpm_children_make(wp, 1 /* in event loop */); + if (restart_child) { + fpm_children_make(wp, 1 /* in event loop */, 1); - if (fpm_globals.is_child) { - break; - } + if (fpm_globals.is_child) { + break; + } + } } else { zlog(ZLOG_STUFF, ZLOG_ALERT, "oops, unknown child exited %s", buf); } @@ -326,14 +346,24 @@ } /* }}} */ -static int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop) /* {{{ */ +int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn) /* {{{ */ { int enough = 0; pid_t pid; struct fpm_child_s *child; + int max; - while (!enough && fpm_pctl_can_spawn_children() && wp->running_children < wp->config->pm->max_children) { + if (wp->config->pm->style == PM_STYLE_DYNAMIC) { + if (!in_event_loop) { /* stating */ + max = wp->config->pm->dynamic.start_servers; + } else { + max = wp->running_children + nb_to_spawn; + } + } else { /* PM_STYLE_STATIC */ + max = wp->config->pm->max_children; + } + while (!enough && fpm_pctl_can_spawn_children() && wp->running_children < max) { child = fpm_resources_prepare(wp); if (!child) { @@ -378,7 +408,7 @@ int fpm_children_create_initial(struct fpm_worker_pool_s *wp) /* {{{ */ { - return fpm_children_make(wp, 0 /* not in event loop yet */); + return fpm_children_make(wp, 0 /* not in event loop yet */, 0); } /* }}} */ Index: sapi/fpm/conf/php-fpm.conf.in =================================================================== --- sapi/fpm/conf/php-fpm.conf.in (révision 292067) +++ sapi/fpm/conf/php-fpm.conf.in (copie de travail) @@ -72,7 +72,7 @@ <value name="pm"> Sets style of controling worker process count. - Valid values are 'static' and 'apache-like' + Valid values are 'static' and 'dynamic' <value name="style">static</value> Sets the limit on the number of simultaneous requests that will be served. @@ -81,20 +81,20 @@ Used with any pm_style. <value name="max_children">5</value> - Settings group for 'apache-like' pm style - <value name="apache_like"> + Settings group for 'dynamic' pm style + <value name="dynamic"> Sets the number of server processes created on startup. - Used only when 'apache-like' pm_style is selected - <value name="StartServers">20</value> + Used only when 'dynamic' pm_style is selected + <value name="start_servers">20</value> Sets the desired minimum number of idle server processes. - Used only when 'apache-like' pm_style is selected - <value name="MinSpareServers">5</value> + Used only when 'dynamic' pm_style is selected + <value name="min_spare_servers">5</value> Sets the desired maximum number of idle server processes. - Used only when 'apache-like' pm_style is selected - <value name="MaxSpareServers">35</value> + Used only when 'dynamic' pm_style is selected + <value name="max_spare_servers">35</value> </value>
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php