static fd_queue_t *worker_queue;
static fd_queue_info_t *worker_queue_info;
static int mpm_state = AP_MPMQ_STARTING;
-static int sick_child_detected;
-static ap_generation_t volatile my_generation = 0;
/* data retained by worker across load/unload of the module
* allocated on first call to pre-config hook; located on
int first_server_limit;
int first_thread_limit;
int module_loads;
+ int sick_child_detected;
+ ap_generation_t my_generation;
+ int volatile is_graceful; /* set from signal handler */
+ int maxclients_reported;
+ int near_maxclients_reported;
+ /*
+ * The max child slot ever assigned, preserved across restarts. Necessary
+ * to deal with MaxClients changes across AP_SIG_GRACEFUL restarts. We
+ * use this value to optimize routines that have to scan the entire
+ * scoreboard.
+ */
+ int max_daemons_limit;
+ /*
+ * idle_spawn_rate is the number of children that will be spawned on the
+ * next maintenance cycle if there aren't enough idle servers. It is
+ * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
+ * without the need to spawn.
+ */
+ int idle_spawn_rate;
+#ifndef MAX_SPAWN_RATE
+#define MAX_SPAWN_RATE (32)
+#endif
+ int hold_off_on_exponential_spawning;
} worker_retained_data;
static worker_retained_data *retained;
#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t)
-/*
- * The max child slot ever assigned, preserved across restarts. Necessary
- * to deal with MaxClients changes across AP_SIG_GRACEFUL restarts. We
- * use this value to optimize routines that have to scan the entire
- * scoreboard.
- */
-static int max_daemons_limit = -1;
-
static ap_worker_pod_t *pod;
/* The worker MPM respects a couple of runtime flags that can aid
*rv = APR_SUCCESS;
switch (query_code) {
case AP_MPMQ_MAX_DAEMON_USED:
- *result = max_daemons_limit;
+ *result = retained->max_daemons_limit;
break;
case AP_MPMQ_IS_THREADED:
*result = AP_MPMQ_STATIC;
*result = mpm_state;
break;
case AP_MPMQ_GENERATION:
- *result = my_generation;
+ *result = retained->my_generation;
break;
default:
*rv = APR_ENOTIMPL;
return OK;
}
-static apr_status_t worker_note_child_killed(int childnum)
+static void worker_note_child_killed(int childnum, pid_t pid, ap_generation_t gen)
{
- ap_scoreboard_image->parent[childnum].pid = 0;
- return APR_SUCCESS;
+ if (childnum != -1) { /* child had a scoreboard slot? */
+ ap_run_child_status(ap_server_conf,
+ ap_scoreboard_image->parent[childnum].pid,
+ ap_scoreboard_image->parent[childnum].generation,
+ childnum, MPM_CHILD_EXITED);
+ ap_scoreboard_image->parent[childnum].pid = 0;
+ }
+ else {
+ ap_run_child_status(ap_server_conf, pid, gen, -1, MPM_CHILD_EXITED);
+ }
+}
+
+static void worker_note_child_started(int slot, pid_t pid)
+{
+ ap_scoreboard_image->parent[slot].pid = pid;
+ ap_run_child_status(ap_server_conf,
+ ap_scoreboard_image->parent[slot].pid,
+ retained->my_generation, slot, MPM_CHILD_STARTED);
+}
+
+static void worker_note_child_lost_slot(int slot, pid_t newpid)
+{
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "pid %" APR_PID_T_FMT " taking over scoreboard slot from "
+ "%" APR_PID_T_FMT "%s",
+ newpid,
+ ap_scoreboard_image->parent[slot].pid,
+ ap_scoreboard_image->parent[slot].quiescing ?
+ " (quiescing)" : "");
+ ap_run_child_status(ap_server_conf,
+ ap_scoreboard_image->parent[slot].pid,
+ ap_scoreboard_image->parent[slot].generation,
+ slot, MPM_CHILD_LOST_SLOT);
+ /* Don't forget about this exiting child process, or we
+ * won't be able to kill it if it doesn't exit by the
+ * time the server is shut down.
+ */
+ ap_register_extra_mpm_process(ap_scoreboard_image->parent[slot].pid,
+ ap_scoreboard_image->parent[slot].generation);
}
static const char *worker_get_name(void)
if (pchild) {
apr_pool_destroy(pchild);
}
+
+ if (one_process) {
+ worker_note_child_killed(/* slot */ 0, 0, 0);
+ }
+
exit(code);
}
* Connection structures and accounting...
*/
-/* volatile just in case */
+static int child_fatal;
+
+/* volatile because they're updated from a signal handler */
static int volatile shutdown_pending;
static int volatile restart_pending;
-static int volatile is_graceful;
-static volatile int child_fatal;
/*
* ap_start_shutdown() and ap_start_restart(), below, are a first stab at
return;
}
shutdown_pending = 1;
- is_graceful = graceful;
+ retained->is_graceful = graceful;
}
/* do a graceful restart if graceful == 1 */
return;
}
restart_pending = 1;
- is_graceful = graceful;
+ retained->is_graceful = graceful;
}
static void sig_term(int sig)
"sigaction(SIGXCPU)");
#endif
#ifdef SIGXFSZ
- sa.sa_handler = SIG_DFL;
+ /* For systems following the LFS standard, ignoring SIGXFSZ allows
+ * a write() beyond the 2GB limit to fail gracefully with E2BIG
+ * rather than terminate the process. */
+ sa.sa_handler = SIG_IGN;
if (sigaction(SIGXFSZ, &sa, NULL) < 0)
ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
"sigaction(SIGXFSZ)");
apr_signal(SIGXCPU, SIG_DFL);
#endif /* SIGXCPU */
#ifdef SIGXFSZ
- apr_signal(SIGXFSZ, SIG_DFL);
+ apr_signal(SIGXFSZ, SIG_IGN);
#endif /* SIGXFSZ */
}
}
/* requests_this_child has gone to zero or below. See if the admin coded
- "MaxRequestsPerChild 0", and keep going in that case. Doing it this way
+ "MaxConnectionsPerChild 0", and keep going in that case. Doing it this way
simplifies the hot path in worker_thread */
static void check_infinite_requests(void)
{
/* apr_pollset_poll() will only return errors in catastrophic
* circumstances. Let's try exiting gracefully, for now. */
- ap_log_error(APLOG_MARK, APLOG_ERR, rv,
- (const server_rec *) ap_server_conf,
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,
"apr_pollset_poll: (listen)");
signal_threads(ST_GRACEFUL);
}
ap_scoreboard_image->servers[process_slot][thread_slot].pid = ap_my_pid;
ap_scoreboard_image->servers[process_slot][thread_slot].tid = apr_os_thread_current();
- ap_scoreboard_image->servers[process_slot][thread_slot].generation = my_generation;
+ ap_scoreboard_image->servers[process_slot][thread_slot].generation = retained->my_generation;
ap_update_child_status_from_indexes(process_slot, thread_slot, SERVER_STARTING, NULL);
#ifdef HAVE_PTHREAD_KILL
else { /* !one_process */
/* remove SIGTERM from the set of blocked signals... if one of
* the other threads in the process needs to take us down
- * (e.g., for MaxRequestsPerChild) it will send us SIGTERM
+ * (e.g., for MaxConnectionsPerChild) it will send us SIGTERM
*/
unblock_signal(SIGTERM);
apr_signal(SIGTERM, dummy_signal_handler);
{
int pid;
- if (slot + 1 > max_daemons_limit) {
- max_daemons_limit = slot + 1;
+ if (slot + 1 > retained->max_daemons_limit) {
+ retained->max_daemons_limit = slot + 1;
}
if (one_process) {
set_signals();
- ap_scoreboard_image->parent[slot].pid = getpid();
+ worker_note_child_started(slot, getpid());
child_main(slot);
+ /* NOTREACHED */
}
if ((pid = fork()) == -1) {
int status = bindprocessor(BINDPROCESS, (int)getpid(),
PROCESSOR_CLASS_ANY);
if (status != OK)
- ap_log_error(APLOG_MARK, APLOG_WARNING, errno,
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, errno,
ap_server_conf,
- "processor unbind failed %d", status);
+ "processor unbind failed");
#endif
RAISE_SIGSTOP(MAKE_CHILD);
apr_signal(SIGTERM, just_die);
child_main(slot);
-
- clean_child_exit(0);
+ /* NOTREACHED */
}
/* else */
if (ap_scoreboard_image->parent[slot].pid != 0) {
/* This new child process is squatting on the scoreboard
* entry owned by an exiting child process, which cannot
* exit until all active requests complete.
- * Don't forget about this exiting child process, or we
- * won't be able to kill it if it doesn't exit by the
- * time the server is shut down.
*/
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
- "taking over scoreboard slot from %" APR_PID_T_FMT "%s",
- ap_scoreboard_image->parent[slot].pid,
- ap_scoreboard_image->parent[slot].quiescing ?
- " (quiescing)" : "");
- ap_register_extra_mpm_process(ap_scoreboard_image->parent[slot].pid);
+ worker_note_child_lost_slot(slot, pid);
}
ap_scoreboard_image->parent[slot].quiescing = 0;
- ap_scoreboard_image->parent[slot].pid = pid;
+ worker_note_child_started(slot, pid);
return 0;
}
}
}
-
-/*
- * idle_spawn_rate is the number of children that will be spawned on the
- * next maintenance cycle if there aren't enough idle servers. It is
- * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
- * without the need to spawn.
- */
-static int idle_spawn_rate = 1;
-#ifndef MAX_SPAWN_RATE
-#define MAX_SPAWN_RATE (32)
-#endif
-static int hold_off_on_exponential_spawning;
-
static void perform_idle_server_maintenance(void)
{
int i, j;
int any_dead_threads = 0;
int all_dead_threads = 1;
- if (i >= max_daemons_limit && totally_free_length == idle_spawn_rate)
+ if (i >= retained->max_daemons_limit && totally_free_length == retained->idle_spawn_rate)
/* short cut if all active processes have been examined and
* enough empty scoreboard slots have been found
*/
loop if no pid? not much else matters */
if (status <= SERVER_READY &&
!ps->quiescing &&
- ps->generation == my_generation) {
+ ps->generation == retained->my_generation) {
++idle_thread_count;
}
if (status >= SERVER_READY && status < SERVER_GRACEFUL) {
}
}
}
- if (any_dead_threads && totally_free_length < idle_spawn_rate
+ if (any_dead_threads && totally_free_length < retained->idle_spawn_rate
&& free_length < MAX_SPAWN_RATE
&& (!ps->pid /* no process in the slot */
|| ps->quiescing)) { /* or at least one is going away */
}
}
- if (sick_child_detected) {
+ if (retained->sick_child_detected) {
if (active_thread_count > 0) {
/* some child processes appear to be working. don't kill the
* whole server.
*/
- sick_child_detected = 0;
+ retained->sick_child_detected = 0;
}
else {
/* looks like a basket case. give up.
}
}
- max_daemons_limit = last_non_dead + 1;
+ retained->max_daemons_limit = last_non_dead + 1;
if (idle_thread_count > max_spare_threads) {
/* Kill off one child */
ap_worker_pod_signal(pod, TRUE);
- idle_spawn_rate = 1;
+ retained->idle_spawn_rate = 1;
}
else if (idle_thread_count < min_spare_threads) {
/* terminate the free list */
/* no threads are "inactive" - starting, stopping, etc. */
/* have we reached MaxClients, or just getting close? */
if (0 == idle_thread_count) {
- static int reported = 0;
- if (!reported) {
+ if (!retained->maxclients_reported) {
/* only report this condition once */
ap_log_error(APLOG_MARK, APLOG_ERR, 0,
ap_server_conf,
"server reached MaxClients setting, consider"
" raising the MaxClients setting");
- reported = 1;
+ retained->maxclients_reported = 1;
}
} else {
- static int reported = 0;
- if (!reported) {
+ if (!retained->near_maxclients_reported) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0,
ap_server_conf,
"server is within MinSpareThreads of MaxClients, "
"consider raising the MaxClients setting");
- reported = 1;
+ retained->near_maxclients_reported = 1;
}
}
}
ap_server_conf,
"scoreboard is full, not at MaxClients");
}
- idle_spawn_rate = 1;
+ retained->idle_spawn_rate = 1;
}
else {
- if (free_length > idle_spawn_rate) {
- free_length = idle_spawn_rate;
+ if (free_length > retained->idle_spawn_rate) {
+ free_length = retained->idle_spawn_rate;
}
- if (idle_spawn_rate >= 8) {
+ if (retained->idle_spawn_rate >= 8) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0,
ap_server_conf,
"server seems busy, (you may need "
/* the next time around we want to spawn twice as many if this
* wasn't good enough, but not if we've just done a graceful
*/
- if (hold_off_on_exponential_spawning) {
- --hold_off_on_exponential_spawning;
+ if (retained->hold_off_on_exponential_spawning) {
+ --retained->hold_off_on_exponential_spawning;
}
- else if (idle_spawn_rate < MAX_SPAWN_RATE) {
- idle_spawn_rate *= 2;
+ else if (retained->idle_spawn_rate < MAX_SPAWN_RATE) {
+ retained->idle_spawn_rate *= 2;
}
}
}
else {
- idle_spawn_rate = 1;
+ retained->idle_spawn_rate = 1;
}
}
static void server_main_loop(int remaining_children_to_start)
{
+ ap_generation_t old_gen;
int child_slot;
apr_exit_why_e exitwhy;
int status, processed_status;
/* tell perform_idle_server_maintenance to check into this
* on the next timer pop
*/
- sick_child_detected = 1;
+ retained->sick_child_detected = 1;
}
/* non-fatal death... note that it's gone in the scoreboard. */
child_slot = ap_find_child_by_pid(&pid);
ap_update_child_status_from_indexes(child_slot, i, SERVER_DEAD,
(request_rec *) NULL);
- ap_scoreboard_image->parent[child_slot].pid = 0;
+ worker_note_child_killed(child_slot, 0, 0);
ap_scoreboard_image->parent[child_slot].quiescing = 0;
if (processed_status == APEXIT_CHILDSICK) {
/* resource shortage, minimize the fork rate */
- idle_spawn_rate = 1;
+ retained->idle_spawn_rate = 1;
}
else if (remaining_children_to_start
&& child_slot < ap_daemons_limit) {
--remaining_children_to_start;
}
}
- else if (ap_unregister_extra_mpm_process(pid.pid) == 1) {
- /* handled */
+ else if (ap_unregister_extra_mpm_process(pid.pid, &old_gen) == 1) {
+ worker_note_child_killed(-1, /* already out of the scoreboard */
+ pid.pid, old_gen);
#if APR_HAS_OTHER_CHILD
}
else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH,
/* handled */
#endif
}
- else if (is_graceful) {
+ else if (retained->is_graceful) {
/* Great, we've probably just lost a slot in the
* scoreboard. Somehow we don't know about this child.
*/
ap_log_pid(pconf, ap_pid_fname);
/* Initialize cross-process accept lock */
- rv = ap_proc_mutex_create(&accept_mutex, AP_ACCEPT_MUTEX_TYPE, NULL, s,
- _pconf, 0);
+ rv = ap_proc_mutex_create(&accept_mutex, NULL, AP_ACCEPT_MUTEX_TYPE, NULL,
+ s, _pconf, 0);
if (rv != APR_SUCCESS) {
mpm_state = AP_MPMQ_STOPPING;
return DONE;
}
- if (!is_graceful) {
+ if (!retained->is_graceful) {
if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
mpm_state = AP_MPMQ_STOPPING;
return DONE;
/* fix the generation number in the global score; we just got a new,
* cleared scoreboard
*/
- ap_scoreboard_image->global->running_generation = my_generation;
+ ap_scoreboard_image->global->running_generation = retained->my_generation;
}
set_signals();
if (remaining_children_to_start > ap_daemons_limit) {
remaining_children_to_start = ap_daemons_limit;
}
- if (!is_graceful) {
+ if (!retained->is_graceful) {
startup_children(remaining_children_to_start);
remaining_children_to_start = 0;
}
else {
/* give the system some time to recover before kicking into
* exponential mode */
- hold_off_on_exponential_spawning = 10;
+ retained->hold_off_on_exponential_spawning = 10;
}
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
ap_get_server_description());
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
"Server built: %s", ap_get_server_built());
+ ap_log_command_line(plog, s);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
"Accept mutex: %s (default: %s)",
apr_proc_mutex_name(accept_mutex),
server_main_loop(remaining_children_to_start);
mpm_state = AP_MPMQ_STOPPING;
- if (shutdown_pending && !is_graceful) {
+ if (shutdown_pending && !retained->is_graceful) {
/* Time to shut down:
* Kill child processes, tell them to call child_exit, etc...
*/
ap_worker_pod_killpg(pod, ap_daemons_limit, FALSE);
- ap_reclaim_child_processes(1); /* Start with SIGTERM */
+ ap_reclaim_child_processes(1, /* Start with SIGTERM */
+ worker_note_child_killed);
if (!child_fatal) {
/* cleanup pid file on normal shutdown */
- const char *pidfile = NULL;
- pidfile = ap_server_root_relative (pconf, ap_pid_fname);
- if ( pidfile != NULL && unlink(pidfile) == 0)
- ap_log_error(APLOG_MARK, APLOG_INFO, 0,
- ap_server_conf,
- "removed PID file %s (pid=%" APR_PID_T_FMT ")",
- pidfile, getpid());
-
+ ap_remove_pid(pconf, ap_pid_fname);
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0,
ap_server_conf, "caught SIGTERM, shutting down");
}
/* Close our listeners, and then ask our children to do same */
ap_close_listeners();
ap_worker_pod_killpg(pod, ap_daemons_limit, TRUE);
- ap_relieve_child_processes();
+ ap_relieve_child_processes(worker_note_child_killed);
if (!child_fatal) {
/* cleanup pid file on normal shutdown */
- const char *pidfile = NULL;
- pidfile = ap_server_root_relative (pconf, ap_pid_fname);
- if ( pidfile != NULL && unlink(pidfile) == 0)
- ap_log_error(APLOG_MARK, APLOG_INFO, 0,
- ap_server_conf,
- "removed PID file %s (pid=%" APR_PID_T_FMT ")",
- pidfile, getpid());
-
+ ap_remove_pid(pconf, ap_pid_fname);
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
"caught " AP_SIG_GRACEFUL_STOP_STRING
", shutting down gracefully");
apr_sleep(apr_time_from_sec(1));
/* Relieve any children which have now exited */
- ap_relieve_child_processes();
+ ap_relieve_child_processes(worker_note_child_killed);
active_children = 0;
for (index = 0; index < ap_daemons_limit; ++index) {
* really dead.
*/
ap_worker_pod_killpg(pod, ap_daemons_limit, FALSE);
- ap_reclaim_child_processes(1);
+ ap_reclaim_child_processes(1, worker_note_child_killed);
return DONE;
}
/* XXX: we really need to make sure this new generation number isn't in
* use by any of the children.
*/
- ++my_generation;
- ap_scoreboard_image->global->running_generation = my_generation;
+ ++retained->my_generation;
+ ap_scoreboard_image->global->running_generation = retained->my_generation;
- if (is_graceful) {
+ if (retained->is_graceful) {
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
AP_SIG_GRACEFUL_STRING " received. Doing graceful restart");
/* wake up the children...time to die. But we'll have more soon */
*/
ap_worker_pod_killpg(pod, ap_daemons_limit, FALSE);
- ap_reclaim_child_processes(1); /* Start with SIGTERM */
+ ap_reclaim_child_processes(1, /* Start with SIGTERM */
+ worker_note_child_killed);
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
"SIGHUP received. Attempting to restart");
}
retained = ap_retained_data_get(userdata_key);
if (!retained) {
retained = ap_retained_data_create(userdata_key, sizeof(*retained));
+ retained->max_daemons_limit = -1;
+ retained->idle_spawn_rate = 1;
}
++retained->module_loads;
if (retained->module_loads == 2) {
- is_graceful = 0;
-
if (!one_process && !foreground) {
rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND
: APR_PROC_DETACH_DAEMONIZE);
return HTTP_INTERNAL_SERVER_ERROR;
}
}
- parent_pid = ap_my_pid = getpid();
}
+ parent_pid = ap_my_pid = getpid();
+
ap_listen_pre_config();
ap_daemons_to_start = DEFAULT_START_DAEMON;
min_spare_threads = DEFAULT_MIN_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD;
ap_daemons_limit = server_limit;
threads_per_child = DEFAULT_THREADS_PER_CHILD;
max_clients = ap_daemons_limit * threads_per_child;
- ap_pid_fname = DEFAULT_PIDLOG;
- ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
ap_extended_status = 0;
- ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
-
- apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir));
return OK;
}
static int worker_check_config(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
- static int restart_num = 0;
int startup = 0;
/* the reverse of pre_config, we want this only the first time around */
- if (restart_num++ == 0) {
+ if (retained->module_loads == 1) {
startup = 1;
}
ap_hook_check_config(worker_check_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_mpm(worker_run, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_mpm_query(worker_query, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_mpm_note_child_killed(worker_note_child_killed, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_mpm_get_name(worker_get_name, NULL, NULL, APR_HOOK_MIDDLE);
}
{ NULL }
};
-module AP_MODULE_DECLARE_DATA mpm_worker_module = {
+AP_DECLARE_MODULE(mpm_worker) = {
MPM20_MODULE_STUFF,
NULL, /* hook to run before apache parses args */
NULL, /* create per-directory config structure */