root/sapi/fpm/fpm/fpm_process_ctl.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. fpm_pctl_cleanup
  2. fpm_pctl_action
  3. fpm_pctl_timeout_set
  4. fpm_pctl_exit
  5. fpm_pctl_exec
  6. fpm_pctl_action_last
  7. fpm_pctl_kill
  8. fpm_pctl_kill_all
  9. fpm_pctl_action_next
  10. fpm_pctl
  11. fpm_pctl_can_spawn_children
  12. fpm_pctl_child_exited
  13. fpm_pctl_init_main
  14. fpm_pctl_check_request_timeout
  15. fpm_pctl_perform_idle_server_maintenance
  16. fpm_pctl_heartbeat
  17. fpm_pctl_perform_idle_server_maintenance_heartbeat
  18. fpm_pctl_on_socket_accept


        /* $Id: fpm_process_ctl.c,v 1.19.2.2 2008/12/13 03:21:18 anight Exp $ */
        /* (c) 2007,2008 Andrei Nigmatulin */

#include "fpm_config.h"

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

#include "fpm.h"
#include "fpm_clock.h"
#include "fpm_children.h"
#include "fpm_signals.h"
#include "fpm_events.h"
#include "fpm_process_ctl.h"
#include "fpm_cleanup.h"
#include "fpm_request.h"
#include "fpm_worker_pool.h"
#include "fpm_scoreboard.h"
#include "fpm_sockets.h"
#include "zlog.h"


static int fpm_state = FPM_PCTL_STATE_NORMAL;
static int fpm_signal_sent = 0;


static const char *fpm_state_names[] = {
        [FPM_PCTL_STATE_NORMAL] = "normal",
        [FPM_PCTL_STATE_RELOADING] = "reloading",
        [FPM_PCTL_STATE_TERMINATING] = "terminating",
        [FPM_PCTL_STATE_FINISHING] = "finishing"
};

static int saved_argc;
static char **saved_argv;

static void fpm_pctl_cleanup(int which, void *arg) /* {{{ */
{
        int i;
        if (which != FPM_CLEANUP_PARENT_EXEC) {
                for (i = 0; i < saved_argc; i++) {
                        free(saved_argv[i]);
                }
                free(saved_argv);
        }
}
/* }}} */

static struct fpm_event_s pctl_event;

static void fpm_pctl_action(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
        fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT);
}
/* }}} */

static int fpm_pctl_timeout_set(int sec) /* {{{ */
{
        fpm_event_set_timer(&pctl_event, 0, &fpm_pctl_action, NULL);
        fpm_event_add(&pctl_event, sec * 1000);
        return 0;
}
/* }}} */

static void fpm_pctl_exit() /* {{{ */
{
        zlog(ZLOG_NOTICE, "exiting, bye-bye!");

        fpm_conf_unlink_pid();
        fpm_cleanups_run(FPM_CLEANUP_PARENT_EXIT_MAIN);
        exit(FPM_EXIT_OK);
}
/* }}} */

#define optional_arg(c) (saved_argc > c ? ", \"" : ""), (saved_argc > c ? saved_argv[c] : ""), (saved_argc > c ? "\"" : "")

static void fpm_pctl_exec() /* {{{ */
{

        zlog(ZLOG_NOTICE, "reloading: execvp(\"%s\", {\"%s\""
                        "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
                        "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
                "})",
                saved_argv[0], saved_argv[0],
                optional_arg(1),
                optional_arg(2),
                optional_arg(3),
                optional_arg(4),
                optional_arg(5),
                optional_arg(6),
                optional_arg(7),
                optional_arg(8),
                optional_arg(9),
                optional_arg(10)
        );

        fpm_cleanups_run(FPM_CLEANUP_PARENT_EXEC);
        execvp(saved_argv[0], saved_argv);
        zlog(ZLOG_SYSERROR, "failed to reload: execvp() failed");
        exit(FPM_EXIT_SOFTWARE);
}
/* }}} */

static void fpm_pctl_action_last() /* {{{ */
{
        switch (fpm_state) {
                case FPM_PCTL_STATE_RELOADING:
                        fpm_pctl_exec();
                        break;

                case FPM_PCTL_STATE_FINISHING:
                case FPM_PCTL_STATE_TERMINATING:
                        fpm_pctl_exit();
                        break;
        }
}
/* }}} */

int fpm_pctl_kill(pid_t pid, int how) /* {{{ */
{
        int s = 0;

        switch (how) {
                case FPM_PCTL_TERM :
                        s = SIGTERM;
                        break;
                case FPM_PCTL_STOP :
                        s = SIGSTOP;
                        break;
                case FPM_PCTL_CONT :
                        s = SIGCONT;
                        break;
                case FPM_PCTL_QUIT :
                        s = SIGQUIT;
                        break;
                default :
                        break;
        }
        return kill(pid, s);
}
/* }}} */

void fpm_pctl_kill_all(int signo) /* {{{ */
{
        struct fpm_worker_pool_s *wp;
        int alive_children = 0;

        for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
                struct fpm_child_s *child;

                for (child = wp->children; child; child = child->next) {
                        int res = kill(child->pid, signo);

                        zlog(ZLOG_DEBUG, "[pool %s] sending signal %d %s to child %d",
                                child->wp->config->name, signo,
                                fpm_signal_names[signo] ? fpm_signal_names[signo] : "", (int) child->pid);

                        if (res == 0) {
                                ++alive_children;
                        }
                }
        }

        if (alive_children) {
                zlog(ZLOG_DEBUG, "%d child(ren) still alive", alive_children);
        }
}
/* }}} */

static void fpm_pctl_action_next() /* {{{ */
{
        int sig, timeout;

        if (!fpm_globals.running_children) {
                fpm_pctl_action_last();
        }

        if (fpm_signal_sent == 0) {
                if (fpm_state == FPM_PCTL_STATE_TERMINATING) {
                        sig = SIGTERM;
                } else {
                        sig = SIGQUIT;
                }
                timeout = fpm_global_config.process_control_timeout;
        } else {
                if (fpm_signal_sent == SIGQUIT) {
                        sig = SIGTERM;
                } else {
                        sig = SIGKILL;
                }
                timeout = 1;
        }

        fpm_pctl_kill_all(sig);
        fpm_signal_sent = sig;
        fpm_pctl_timeout_set(timeout);
}
/* }}} */

void fpm_pctl(int new_state, int action) /* {{{ */
{
        switch (action) {
                case FPM_PCTL_ACTION_SET :
                        if (fpm_state == new_state) { /* already in progress - just ignore duplicate signal */
                                return;
                        }

                        switch (fpm_state) { /* check which states can be overridden */
                                case FPM_PCTL_STATE_NORMAL :
                                        /* 'normal' can be overridden by any other state */
                                        break;
                                case FPM_PCTL_STATE_RELOADING :
                                        /* 'reloading' can be overridden by 'finishing' */
                                        if (new_state == FPM_PCTL_STATE_FINISHING) break;
                                case FPM_PCTL_STATE_FINISHING :
                                        /* 'reloading' and 'finishing' can be overridden by 'terminating' */
                                        if (new_state == FPM_PCTL_STATE_TERMINATING) break;
                                case FPM_PCTL_STATE_TERMINATING :
                                        /* nothing can override 'terminating' state */
                                        zlog(ZLOG_DEBUG, "not switching to '%s' state, because already in '%s' state",
                                                fpm_state_names[new_state], fpm_state_names[fpm_state]);
                                        return;
                        }

                        fpm_signal_sent = 0;
                        fpm_state = new_state;

                        zlog(ZLOG_DEBUG, "switching to '%s' state", fpm_state_names[fpm_state]);
                        /* fall down */

                case FPM_PCTL_ACTION_TIMEOUT :
                        fpm_pctl_action_next();
                        break;
                case FPM_PCTL_ACTION_LAST_CHILD_EXITED :
                        fpm_pctl_action_last();
                        break;

        }
}
/* }}} */

int fpm_pctl_can_spawn_children() /* {{{ */
{
        return fpm_state == FPM_PCTL_STATE_NORMAL;
}
/* }}} */

int fpm_pctl_child_exited() /* {{{ */
{
        if (fpm_state == FPM_PCTL_STATE_NORMAL) {
                return 0;
        }

        if (!fpm_globals.running_children) {
                fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_LAST_CHILD_EXITED);
        }
        return 0;
}
/* }}} */

int fpm_pctl_init_main() /* {{{ */
{
        int i;

        saved_argc = fpm_globals.argc;
        saved_argv = malloc(sizeof(char *) * (saved_argc + 1));

        if (!saved_argv) {
                return -1;
        }

        for (i = 0; i < saved_argc; i++) {
                saved_argv[i] = strdup(fpm_globals.argv[i]);

                if (!saved_argv[i]) {
                        return -1;
                }
        }

        saved_argv[i] = 0;

        if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_pctl_cleanup, 0)) {
                return -1;
        }
        return 0;
}
/* }}} */

static void fpm_pctl_check_request_timeout(struct timeval *now) /* {{{ */
{
        struct fpm_worker_pool_s *wp;

        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;

                if (terminate_timeout || slowlog_timeout) {
                        for (child = wp->children; child; child = child->next) {
                                fpm_request_check_timed_out(child, now, terminate_timeout, slowlog_timeout);
                        }
                }
        }
}
/* }}} */

static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{ */
{
        struct fpm_worker_pool_s *wp;

        for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
                struct fpm_child_s *child;
                struct fpm_child_s *last_idle_child = NULL;
                int idle = 0;
                int active = 0;
                int children_to_fork;
                unsigned cur_lq = 0;

                if (wp->config == NULL) continue;

                for (child = wp->children; child; child = child->next) {
                        if (fpm_request_is_idle(child)) {
                                if (last_idle_child == NULL) {
                                        last_idle_child = child;
                                } else {
                                        if (timercmp(&child->started, &last_idle_child->started, <)) {
                                                last_idle_child = child;
                                        }
                                }
                                idle++;
                        } else {
                                active++;
                        }
                }

                /* update status structure for all PMs */
                if (wp->listen_address_domain == FPM_AF_INET) {
                        if (0 > fpm_socket_get_listening_queue(wp->listening_socket, &cur_lq, NULL)) {
                                cur_lq = 0;
#if 0
                        } else {
                                if (cur_lq > 0) {
                                        if (!wp->warn_lq) {
                                                zlog(ZLOG_WARNING, "[pool %s] listening queue is not empty, #%d requests are waiting to be served, consider raising pm.max_children setting (%d)", wp->config->name, cur_lq, wp->config->pm_max_children);
                                                wp->warn_lq = 1;
                                        }
                                } else {
                                        wp->warn_lq = 0;
                                }
#endif
                        }
                }
                fpm_scoreboard_update(idle, active, cur_lq, -1, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard);

                /* this is specific to PM_STYLE_ONDEMAND */
                if (wp->config->pm == PM_STYLE_ONDEMAND) {
                        struct timeval last, now;

                        zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children", wp->config->name, active, idle);

                        if (!last_idle_child) continue;

                        fpm_request_last_activity(last_idle_child, &last);
                        fpm_clock_get(&now);
                        if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) {
                                last_idle_child->idle_kill = 1;
                                fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
                        }

                        continue;
                }

                /* the rest is only used by PM_STYLE_DYNAMIC */
                if (wp->config->pm != PM_STYLE_DYNAMIC) continue;

                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);

                if (idle > wp->config->pm_max_spare_servers && last_idle_child) {
                        last_idle_child->idle_kill = 1;
                        fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
                        wp->idle_spawn_rate = 1;
                        continue;
                }

                if (idle < wp->config->pm_min_spare_servers) {
                        if (wp->running_children >= wp->config->pm_max_children) {
                                if (!wp->warn_max_children) {
                                        fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
                                        zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
                                        wp->warn_max_children = 1;
                                }
                                wp->idle_spawn_rate = 1;
                                continue;
                        }

                        if (wp->idle_spawn_rate >= 8) {
                                zlog(ZLOG_WARNING, "[pool %s] seems busy (you may need to increase pm.start_servers, or pm.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);
                        }

                        /* compute the number of idle process to spawn */
                        children_to_fork = MIN(wp->idle_spawn_rate, wp->config->pm_min_spare_servers - idle);

                        /* get sure it won't exceed max_children */
                        children_to_fork = MIN(children_to_fork, wp->config->pm_max_children - wp->running_children);
                        if (children_to_fork <= 0) {
                                if (!wp->warn_max_children) {
                                        fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
                                        zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
                                        wp->warn_max_children = 1;
                                }
                                wp->idle_spawn_rate = 1;
                                continue;
                        }
                        wp->warn_max_children = 0;

                        fpm_children_make(wp, 1, children_to_fork, 1);

                        /* 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_DEBUG, "[pool %s] %d child(ren) have been created dynamically", wp->config->name, children_to_fork);  

                        /* 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(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
        static struct fpm_event_s heartbeat;
        struct timeval now;

        if (fpm_globals.parent_pid != getpid()) {
                return; /* sanity check */
        }

        if (which == FPM_EV_TIMEOUT) {
                fpm_clock_get(&now);
                fpm_pctl_check_request_timeout(&now);
                return;
        }

        /* ensure heartbeat is not lower than FPM_PCTL_MIN_HEARTBEAT */
        fpm_globals.heartbeat = MAX(fpm_globals.heartbeat, FPM_PCTL_MIN_HEARTBEAT);

        /* first call without setting to initialize the timer */
        zlog(ZLOG_DEBUG, "heartbeat have been set up with a timeout of %dms", fpm_globals.heartbeat);
        fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_heartbeat, NULL);
        fpm_event_add(&heartbeat, fpm_globals.heartbeat);
}
/* }}} */

void fpm_pctl_perform_idle_server_maintenance_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
        static struct fpm_event_s heartbeat;
        struct timeval now;

        if (fpm_globals.parent_pid != getpid()) {
                return; /* sanity check */
        }

        if (which == FPM_EV_TIMEOUT) {
                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;
                        }
                }
                return;
        }

        /* first call without setting which to initialize the timer */
        fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_perform_idle_server_maintenance_heartbeat, NULL);
        fpm_event_add(&heartbeat, FPM_IDLE_SERVER_MAINTENANCE_HEARTBEAT);
}
/* }}} */

void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
        struct fpm_worker_pool_s *wp = (struct fpm_worker_pool_s *)arg;
        struct fpm_child_s *child;


        if (fpm_globals.parent_pid != getpid()) {
                /* prevent a event race condition when child process
                 * have not set up its own event loop */
                return;
        }

        wp->socket_event_set = 0;

/*      zlog(ZLOG_DEBUG, "[pool %s] heartbeat running_children=%d", wp->config->name, wp->running_children);*/

        if (wp->running_children >= wp->config->pm_max_children) {
                if (!wp->warn_max_children) {
                        fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
                        zlog(ZLOG_WARNING, "[pool %s] server reached max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
                        wp->warn_max_children = 1;
                }

                return;
        }

        for (child = wp->children; child; child = child->next) {
                /* if there is at least on idle child, it will handle the connection, stop here */
                if (fpm_request_is_idle(child)) {
                        return;
                }
        }

        wp->warn_max_children = 0;
        fpm_children_make(wp, 1, 1, 1);

        if (fpm_globals.is_child) {
                return;
        }

        zlog(ZLOG_DEBUG, "[pool %s] got accept without idle child available .... I forked", wp->config->name);
}
/* }}} */


/* [<][>][^][v][top][bottom][index][help] */