diff options
Diffstat (limited to '')
40 files changed, 18162 insertions, 0 deletions
diff --git a/server/mpm/MPM.NAMING b/server/mpm/MPM.NAMING new file mode 100644 index 0000000..c07884d --- /dev/null +++ b/server/mpm/MPM.NAMING @@ -0,0 +1,14 @@ + +The following MPMs currently exist: + + prefork ....... Multi Process Model with Preforking (Apache 1.3) + mpmt_os2 ...... Multi Process Model with Threading on OS/2 + Constant number of processes, variable number of threads. + One acceptor thread per process, multiple workers threads. + winnt ......... Single Process Model with Threading on Windows NT + event ......... Multi Process model with threads. One acceptor thread, + multiple worker threads, separate poller threads for idle + connections and asynchoneous write completion. + worker ........ Multi Process model with threads. One acceptor thread, + multiple worker threads. + netware ....... Multi-threaded MPM for Netware diff --git a/server/mpm/Makefile.in b/server/mpm/Makefile.in new file mode 100644 index 0000000..a158f8b --- /dev/null +++ b/server/mpm/Makefile.in @@ -0,0 +1,4 @@ + +SUBDIRS = $(MPM_SUBDIRS) + +include $(top_builddir)/build/rules.mk diff --git a/server/mpm/config.m4 b/server/mpm/config.m4 new file mode 100644 index 0000000..6d3ab86 --- /dev/null +++ b/server/mpm/config.m4 @@ -0,0 +1,128 @@ +dnl common platform checks needed by MPMs, methods for MPMs to state +dnl their support for the platform, functions to query MPM properties + +APR_CHECK_APR_DEFINE(APR_HAS_THREADS) + +have_threaded_sig_graceful=yes +case $host in + *-linux-*) + case `uname -r` in + 2.0* ) + dnl Threaded MPM's are not supported on Linux 2.0 + dnl as on 2.0 the linuxthreads library uses SIGUSR1 + dnl and SIGUSR2 internally + have_threaded_sig_graceful=no + ;; + esac + ;; +esac + +dnl See if APR supports APR_POLLSET_THREADSAFE. +dnl XXX This hack tests for the underlying functions used by APR when it supports +dnl XXX APR_POLLSET_THREADSAFE, and duplicates APR's Darwin version check. +dnl A run-time check for +dnl apr_pollset_create(,,APR_POLLSET_THREADSAFE) == APR_SUCCESS +dnl would be great but an in-tree apr (srclib/apr) hasn't been built yet. + +AC_CACHE_CHECK([whether APR supports thread-safe pollsets], [ac_cv_have_threadsafe_pollset], [ + case $host in + *-apple-darwin[[1-9]].*) + APR_SETIFNULL(ac_cv_func_kqueue, [no]) + ;; + esac + AC_CHECK_FUNCS(kqueue port_create epoll_create) + if test "$ac_cv_func_kqueue$ac_cv_func_port_create$ac_cv_func_epoll_create" != "nonono"; then + ac_cv_have_threadsafe_pollset=yes + else + ac_cv_have_threadsafe_pollset=no + fi +]) + +dnl See if APR has skiplist +dnl The base httpd prereq is APR 1.4.x, so we don't have to consider +dnl earlier versions. +case $APR_VERSION in + 1.4*) + apr_has_skiplist=no + ;; + *) + apr_has_skiplist=yes +esac + +dnl See if this is a forking platform w.r.t. MPMs +case $host in + *mingw32* | *os2-emx*) + forking_mpms_supported=no + ;; + *) + forking_mpms_supported=yes + ;; +esac + +dnl APACHE_MPM_SUPPORTED(name, supports-shared, is_threaded) +AC_DEFUN([APACHE_MPM_SUPPORTED],[ + if test "$2" = "yes"; then + eval "ap_supported_mpm_$1=shared" + ap_supported_shared_mpms="$ap_supported_shared_mpms $1 " + else + eval "ap_supported_mpm_$1=static" + fi + if test "$3" = "yes"; then + eval "ap_threaded_mpm_$1=yes" + fi +])dnl + +dnl APACHE_MPM_ENABLED(name) +AC_DEFUN([APACHE_MPM_ENABLED],[ + if ap_mpm_is_enabled $1; then + : + else + eval "ap_enabled_mpm_$1=yes" + ap_enabled_mpms="$ap_enabled_mpms $1 " + fi +])dnl + +ap_mpm_is_supported () +{ + eval "tmp=\$ap_supported_mpm_$1" + if test -z "$tmp"; then + return 1 + else + return 0 + fi +} + +ap_mpm_supports_shared () +{ + eval "tmp=\$ap_supported_mpm_$1" + if test "$tmp" = "shared"; then + return 0 + else + return 1 + fi +} + +ap_mpm_is_threaded () +{ + if test "$mpm_build" = "shared" -a ac_cv_define_APR_HAS_THREADS = "yes"; then + return 0 + fi + + for mpm in $ap_enabled_mpms; do + eval "tmp=\$ap_threaded_mpm_$mpm" + if test "$tmp" = "yes"; then + return 0 + fi + done + return 1 +} + +ap_mpm_is_enabled () +{ + eval "tmp=\$ap_enabled_mpm_$1" + if test "$tmp" = "yes"; then + return 0 + else + return 1 + fi +} diff --git a/server/mpm/config2.m4 b/server/mpm/config2.m4 new file mode 100644 index 0000000..d7e73ec --- /dev/null +++ b/server/mpm/config2.m4 @@ -0,0 +1,89 @@ +AC_MSG_CHECKING(which MPM to use by default) +AC_ARG_WITH(mpm, +APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use by default. + MPM={event|worker|prefork|winnt} + This will be statically linked as the only available MPM unless + --enable-mpms-shared is also specified. +),[ + default_mpm=$withval + AC_MSG_RESULT($withval); +],[ + dnl Order of preference for default MPM: + dnl The Windows and OS/2 MPMs are used on those platforms. + dnl Everywhere else: event, worker, prefork + if ap_mpm_is_supported "winnt"; then + default_mpm=winnt + AC_MSG_RESULT(winnt) + elif ap_mpm_is_supported "mpmt_os2"; then + default_mpm=mpmt_os2 + AC_MSG_RESULT(mpmt_os2) + elif ap_mpm_is_supported "event"; then + default_mpm=event + AC_MSG_RESULT(event) + elif ap_mpm_is_supported "worker"; then + default_mpm=worker + AC_MSG_RESULT(worker - event is not supported) + else + default_mpm=prefork + AC_MSG_RESULT(prefork - event and worker are not supported) + fi +]) + +APACHE_MPM_ENABLED($default_mpm) + +AC_ARG_ENABLE(mpms-shared, +APACHE_HELP_STRING(--enable-mpms-shared=MPM-LIST,Space-separated list of MPM modules to enable for dynamic loading. MPM-LIST=list | "all"),[ + if test "$enableval" = "no"; then + mpm_build=static + else + mpm_build=shared +dnl Build just the default MPM if --enable-mpms-shared has no argument. + if test "$enableval" = "yes"; then + enableval=$default_mpm + fi + for i in $enableval; do + if test "$i" = "all"; then + for j in $ap_supported_shared_mpms; do + eval "enable_mpm_$j=shared" + APACHE_MPM_ENABLED($j) + done + else + i=`echo $i | sed 's/-/_/g'` + if ap_mpm_supports_shared $i; then + eval "enable_mpm_$i=shared" + APACHE_MPM_ENABLED($i) + else + AC_MSG_ERROR([MPM $i does not support dynamic loading.]) + fi + fi + done + fi +], [mpm_build=static]) + +for i in $ap_enabled_mpms; do + if ap_mpm_is_supported $i; then + : + else + AC_MSG_ERROR([MPM $i is not supported on this platform.]) + fi +done + +if test $mpm_build = "shared"; then + eval "tmp=\$enable_mpm_$default_mpm" + if test "$tmp" != "shared"; then + AC_MSG_ERROR([The default MPM ($default_mpm) must be included in --enable-mpms-shared. Use --with-mpm to change the default MPM.]) + fi +fi + +APACHE_FAST_OUTPUT(server/mpm/Makefile) + +if test $mpm_build = "shared"; then + MPM_LIB="" +else + MPM_LIB=server/mpm/$default_mpm/lib${default_mpm}.la + MODLIST="$MODLIST mpm_${default_mpm}" +fi + +MPM_SUBDIRS=$ap_enabled_mpms +APACHE_SUBST(MPM_SUBDIRS) +APACHE_SUBST(MPM_LIB) diff --git a/server/mpm/event/Makefile.in b/server/mpm/event/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/event/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/event/config.m4 b/server/mpm/event/config.m4 new file mode 100644 index 0000000..c891c75 --- /dev/null +++ b/server/mpm/event/config.m4 @@ -0,0 +1,15 @@ +AC_MSG_CHECKING(if event MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +elif test $ac_cv_define_APR_HAS_THREADS != yes; then + AC_MSG_RESULT(no - APR does not support threads) +elif test $have_threaded_sig_graceful != yes; then + AC_MSG_RESULT(no - SIG_GRACEFUL cannot be used with a threaded MPM) +elif test $ac_cv_have_threadsafe_pollset != yes; then + AC_MSG_RESULT(no - APR_POLLSET_THREADSAFE is not supported) +elif test $apr_has_skiplist != yes; then + AC_MSG_RESULT(no - APR skiplist is not available, need APR 1.5.x or later) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(event, yes, yes) +fi diff --git a/server/mpm/event/config3.m4 b/server/mpm/event/config3.m4 new file mode 100644 index 0000000..09d3626 --- /dev/null +++ b/server/mpm/event/config3.m4 @@ -0,0 +1,7 @@ +dnl ## XXX - Need a more thorough check of the proper flags to use + +APACHE_SUBST(MOD_MPM_EVENT_LDADD) + +APACHE_MPM_MODULE(event, $enable_mpm_event, event.lo,[ + AC_CHECK_FUNCS(pthread_kill) +], , [\$(MOD_MPM_EVENT_LDADD)]) diff --git a/server/mpm/event/event.c b/server/mpm/event/event.c new file mode 100644 index 0000000..3672f44 --- /dev/null +++ b/server/mpm/event/event.c @@ -0,0 +1,4078 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This MPM tries to fix the 'keep alive problem' in HTTP. + * + * After a client completes the first request, the client can keep the + * connection open to send more requests with the same socket. This can save + * significant overhead in creating TCP connections. However, the major + * disadvantage is that Apache traditionally keeps an entire child + * process/thread waiting for data from the client. To solve this problem, + * this MPM has a dedicated thread for handling both the Listening sockets, + * and all sockets that are in a Keep Alive status. + * + * The MPM assumes the underlying apr_pollset implementation is somewhat + * threadsafe. This currently is only compatible with KQueue and EPoll. This + * enables the MPM to avoid extra high level locking or having to wake up the + * listener thread when a keep-alive socket needs to be sent to it. + * + * This MPM does not perform well on older platforms that do not have very good + * threading, like Linux with a 2.4 kernel, but this does not matter, since we + * require EPoll or KQueue. + * + * For FreeBSD, use 5.3. It is possible to run this MPM on FreeBSD 5.2.1, if + * you use libkse (see `man libmap.conf`). + * + * For NetBSD, use at least 2.0. + * + * For Linux, you should use a 2.6 kernel, and make sure your glibc has epoll + * support compiled in. + * + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_file_io.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_thread_mutex.h" +#include "apr_poll.h" +#include "apr_ring.h" +#include "apr_queue.h" +#include "apr_atomic.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_version.h" + +#include <stdlib.h> + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if APR_HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifdef HAVE_SYS_PROCESSOR_H +#include <sys/processor.h> /* for bindprocessor() */ +#endif + +#if !APR_HAS_THREADS +#error The Event MPM requires APR threads, but they are unavailable. +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "http_protocol.h" +#include "ap_mpm.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "mpm_fdqueue.h" +#include "mpm_default.h" +#include "http_vhost.h" +#include "unixd.h" +#include "apr_skiplist.h" + +#include <signal.h> +#include <limits.h> /* for INT_MAX */ + + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_SERVER_LIMIT +#define DEFAULT_SERVER_LIMIT 16 +#endif + +/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_SERVER_LIMIT +#define MAX_SERVER_LIMIT 20000 +#endif + +/* Limit on the threads per process. Clients will be locked out if more than + * this are needed. + * + * We keep this for one reason it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 64 +#endif + +/* Admin can't tune ThreadLimit beyond MAX_THREAD_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 100000 +#endif + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) + +#if !APR_VERSION_AT_LEAST(1,4,0) +#define apr_time_from_msec(x) (x * 1000) +#endif + +#ifndef MAX_SECS_TO_LINGER +#define MAX_SECS_TO_LINGER 30 +#endif +#define SECONDS_TO_LINGER 2 + +/* + * Actual definitions of config globals + */ + +#ifndef DEFAULT_WORKER_FACTOR +#define DEFAULT_WORKER_FACTOR 2 +#endif +#define WORKER_FACTOR_SCALE 16 /* scale factor to allow fractional values */ +static unsigned int worker_factor = DEFAULT_WORKER_FACTOR * WORKER_FACTOR_SCALE; + /* AsyncRequestWorkerFactor * 16 */ + +static int threads_per_child = 0; /* ThreadsPerChild */ +static int ap_daemons_to_start = 0; /* StartServers */ +static int min_spare_threads = 0; /* MinSpareThreads */ +static int max_spare_threads = 0; /* MaxSpareThreads */ +static int active_daemons_limit = 0; /* MaxRequestWorkers / ThreadsPerChild */ +static int max_workers = 0; /* MaxRequestWorkers */ +static int server_limit = 0; /* ServerLimit */ +static int thread_limit = 0; /* ThreadLimit */ +static int had_healthy_child = 0; +static volatile int dying = 0; +static volatile int workers_may_exit = 0; +static volatile int start_thread_may_exit = 0; +static volatile int listener_may_exit = 0; +static int listener_is_wakeable = 0; /* Pollset supports APR_POLLSET_WAKEABLE */ +static int num_listensocks = 0; +static apr_int32_t conns_this_child; /* MaxConnectionsPerChild, only access + in listener thread */ +static apr_uint32_t connection_count = 0; /* Number of open connections */ +static apr_uint32_t lingering_count = 0; /* Number of connections in lingering close */ +static apr_uint32_t suspended_count = 0; /* Number of suspended connections */ +static apr_uint32_t clogged_count = 0; /* Number of threads processing ssl conns */ +static apr_uint32_t threads_shutdown = 0; /* Number of threads that have shutdown + early during graceful termination */ +static int resource_shortage = 0; +static fd_queue_t *worker_queue; +static fd_queue_info_t *worker_queue_info; + +static apr_thread_mutex_t *timeout_mutex; + +module AP_MODULE_DECLARE_DATA mpm_event_module; + +/* forward declare */ +struct event_srv_cfg_s; +typedef struct event_srv_cfg_s event_srv_cfg; + +static apr_pollfd_t *listener_pollfd; + +/* + * The pollset for sockets that are in any of the timeout queues. Currently + * we use the timeout_mutex to make sure that connections are added/removed + * atomically to/from both event_pollset and a timeout queue. Otherwise + * some confusion can happen under high load if timeout queues and pollset + * get out of sync. + * XXX: It should be possible to make the lock unnecessary in many or even all + * XXX: cases. + */ +static apr_pollset_t *event_pollset; + +typedef struct event_conn_state_t event_conn_state_t; + +/* + * The chain of connections to be shutdown by a worker thread (deferred), + * linked list updated atomically. + */ +static event_conn_state_t *volatile defer_linger_chain; + +struct event_conn_state_t { + /** APR_RING of expiration timeouts */ + APR_RING_ENTRY(event_conn_state_t) timeout_list; + /** the time when the entry was queued */ + apr_time_t queue_timestamp; + /** connection record this struct refers to */ + conn_rec *c; + /** request record (if any) this struct refers to */ + request_rec *r; + /** server config this struct refers to */ + event_srv_cfg *sc; + /** scoreboard handle for the conn_rec */ + ap_sb_handle_t *sbh; + /** is the current conn_rec suspended? (disassociated with + * a particular MPM thread; for suspend_/resume_connection + * hooks) + */ + int suspended; + /** memory pool to allocate from */ + apr_pool_t *p; + /** bucket allocator */ + apr_bucket_alloc_t *bucket_alloc; + /** poll file descriptor information */ + apr_pollfd_t pfd; + /** public parts of the connection state */ + conn_state_t pub; + /** chaining in defer_linger_chain */ + struct event_conn_state_t *chain; + /** Is lingering close from defer_lingering_close()? */ + int deferred_linger; +}; + +APR_RING_HEAD(timeout_head_t, event_conn_state_t); + +struct timeout_queue { + struct timeout_head_t head; + apr_interval_time_t timeout; + apr_uint32_t count; /* for this queue */ + apr_uint32_t *total; /* for all chained/related queues */ + struct timeout_queue *next; /* chaining */ +}; +/* + * Several timeout queues that use different timeouts, so that we always can + * simply append to the end. + * write_completion_q uses vhost's TimeOut + * keepalive_q uses vhost's KeepAliveTimeOut + * linger_q uses MAX_SECS_TO_LINGER + * short_linger_q uses SECONDS_TO_LINGER + */ +static struct timeout_queue *write_completion_q, + *keepalive_q, + *linger_q, + *short_linger_q; +static volatile apr_time_t queues_next_expiry; + +/* Prevent extra poll/wakeup calls for timeouts close in the future (queues + * have the granularity of a second anyway). + * XXX: Wouldn't 0.5s (instead of 0.1s) be "enough"? + */ +#define TIMEOUT_FUDGE_FACTOR apr_time_from_msec(100) + +/* + * Macros for accessing struct timeout_queue. + * For TO_QUEUE_APPEND and TO_QUEUE_REMOVE, timeout_mutex must be held. + */ +static void TO_QUEUE_APPEND(struct timeout_queue *q, event_conn_state_t *el) +{ + apr_time_t elem_expiry; + apr_time_t next_expiry; + + APR_RING_INSERT_TAIL(&q->head, el, event_conn_state_t, timeout_list); + ++*q->total; + ++q->count; + + /* Cheaply update the global queues_next_expiry with the one of the + * first entry of this queue (oldest) if it expires before. + */ + el = APR_RING_FIRST(&q->head); + elem_expiry = el->queue_timestamp + q->timeout; + next_expiry = queues_next_expiry; + if (!next_expiry || next_expiry > elem_expiry + TIMEOUT_FUDGE_FACTOR) { + queues_next_expiry = elem_expiry; + /* Unblock the poll()ing listener for it to update its timeout. */ + if (listener_is_wakeable) { + apr_pollset_wakeup(event_pollset); + } + } +} + +static void TO_QUEUE_REMOVE(struct timeout_queue *q, event_conn_state_t *el) +{ + APR_RING_REMOVE(el, timeout_list); + APR_RING_ELEM_INIT(el, timeout_list); + --*q->total; + --q->count; +} + +static struct timeout_queue *TO_QUEUE_MAKE(apr_pool_t *p, apr_time_t t, + struct timeout_queue *ref) +{ + struct timeout_queue *q; + + q = apr_pcalloc(p, sizeof *q); + APR_RING_INIT(&q->head, event_conn_state_t, timeout_list); + q->total = (ref) ? ref->total : apr_pcalloc(p, sizeof *q->total); + q->timeout = t; + + return q; +} + +#define TO_QUEUE_ELEM_INIT(el) \ + APR_RING_ELEM_INIT((el), timeout_list) + +/* The structure used to pass unique initialization info to each thread */ +typedef struct +{ + int pslot; /* process slot */ + int tslot; /* worker slot of the thread */ +} proc_info; + +/* Structure used to pass information to the thread responsible for + * creating the rest of the threads. + */ +typedef struct +{ + apr_thread_t **threads; + apr_thread_t *listener; + int child_num_arg; + apr_threadattr_t *threadattr; +} thread_starter; + +typedef enum +{ + PT_CSD, + PT_ACCEPT +} poll_type_e; + +typedef struct +{ + poll_type_e type; + void *baton; +} listener_poll_type; + +/* data retained by event across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook + */ +typedef struct event_retained_data { + ap_unixd_mpm_retained_data *mpm; + + int first_server_limit; + int first_thread_limit; + int sick_child_detected; + int maxclients_reported; + int near_maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts. + * We use this value to optimize routines that have to scan the entire + * scoreboard. + */ + int max_daemon_used; + + /* + * All running workers, active and shutting down, including those that + * may be left from before a graceful restart. + * Not kept up-to-date when shutdown is pending. + */ + int total_daemons; + /* + * Workers that still active, i.e. are not shutting down gracefully. + */ + int active_daemons; + /* + * 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 + * maintained per listeners bucket, 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; +} event_retained_data; +static event_retained_data *retained; + +typedef struct event_child_bucket { + ap_pod_t *pod; + ap_listen_rec *listeners; +} event_child_bucket; +static event_child_bucket *all_buckets, /* All listeners buckets */ + *my_bucket; /* Current child bucket */ + +struct event_srv_cfg_s { + struct timeout_queue *wc_q, + *ka_q; +}; + +#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t) + +/* The event MPM respects a couple of runtime flags that can aid + * in debugging. Setting the -DNO_DETACH flag will prevent the root process + * from detaching from its controlling terminal. Additionally, setting + * the -DONE_PROCESS flag (which implies -DNO_DETACH) will get you the + * child_main loop running in the process which originally started up. + * This gives you a pretty nice debugging environment. (You'll get a SIGHUP + * early in standalone_main; just continue through. This is the server + * trying to kill off any child processes which it might have lying + * around --- Apache doesn't keep track of their pids, it just sends + * SIGHUP to the process group, ignoring it in the root process. + * Continue through and you'll be fine.). + */ + +static int one_process = 0; + +#ifdef DEBUG_SIGSTOP +int raise_sigstop_flags; +#endif + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pchild; /* Pool for httpd child stuff */ +static apr_pool_t *pruntime; /* Pool for MPM threads stuff */ + +static pid_t ap_my_pid; /* Linux getpid() doesn't work except in main + thread. Use this instead */ +static pid_t parent_pid; +static apr_os_thread_t *listener_os_thread; + +static int ap_child_slot; /* Current child process slot in scoreboard */ + +/* The LISTENER_SIGNAL signal will be sent from the main thread to the + * listener thread to wake it up for graceful termination (what a child + * process from an old generation does when the admin does "apachectl + * graceful"). This signal will be blocked in all threads of a child + * process except for the listener thread. + */ +#define LISTENER_SIGNAL SIGHUP + +/* An array of socket descriptors in use by each thread used to + * perform a non-graceful (forced) shutdown of the server. + */ +static apr_socket_t **worker_sockets; + +static volatile apr_uint32_t listensocks_disabled; + +static void disable_listensocks(void) +{ + int i; + if (apr_atomic_cas32(&listensocks_disabled, 1, 0) != 0) { + return; + } + if (event_pollset) { + for (i = 0; i < num_listensocks; i++) { + apr_pollset_remove(event_pollset, &listener_pollfd[i]); + } + } + ap_scoreboard_image->parent[ap_child_slot].not_accepting = 1; +} + +static void enable_listensocks(void) +{ + int i; + if (listener_may_exit + || apr_atomic_cas32(&listensocks_disabled, 0, 1) != 1) { + return; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00457) + "Accepting new connections again: " + "%u active conns (%u lingering/%u clogged/%u suspended), " + "%u idle workers", + apr_atomic_read32(&connection_count), + apr_atomic_read32(&lingering_count), + apr_atomic_read32(&clogged_count), + apr_atomic_read32(&suspended_count), + ap_queue_info_num_idlers(worker_queue_info)); + for (i = 0; i < num_listensocks; i++) + apr_pollset_add(event_pollset, &listener_pollfd[i]); + /* + * XXX: This is not yet optimal. If many workers suddenly become available, + * XXX: the parent may kill some processes off too soon. + */ + ap_scoreboard_image->parent[ap_child_slot].not_accepting = 0; +} + +static APR_INLINE apr_uint32_t listeners_disabled(void) +{ + return apr_atomic_read32(&listensocks_disabled); +} + +static APR_INLINE int connections_above_limit(int *busy) +{ + apr_uint32_t i_count = ap_queue_info_num_idlers(worker_queue_info); + if (i_count > 0) { + apr_uint32_t c_count = apr_atomic_read32(&connection_count); + apr_uint32_t l_count = apr_atomic_read32(&lingering_count); + if (c_count <= l_count + /* Off by 'listeners_disabled()' to avoid flip flop */ + || c_count - l_count < (apr_uint32_t)threads_per_child + + (i_count - listeners_disabled()) * + (worker_factor / WORKER_FACTOR_SCALE)) { + return 0; + } + } + else if (busy) { + *busy = 1; + } + return 1; +} + +static APR_INLINE int should_enable_listensocks(void) +{ + return !dying && listeners_disabled() && !connections_above_limit(NULL); +} + +static void close_socket_nonblocking_(apr_socket_t *csd, + const char *from, int line) +{ + apr_status_t rv; + apr_os_sock_t fd = -1; + + /* close_worker_sockets() may have closed it already */ + rv = apr_os_sock_get(&fd, csd); + ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, + "closing socket %i/%pp from %s:%i", (int)fd, csd, from, line); + if (rv == APR_SUCCESS && fd == -1) { + return; + } + + apr_socket_timeout_set(csd, 0); + rv = apr_socket_close(csd); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00468) + "error closing socket"); + AP_DEBUG_ASSERT(0); + } +} +#define close_socket_nonblocking(csd) \ + close_socket_nonblocking_(csd, __FUNCTION__, __LINE__) + +static void close_worker_sockets(void) +{ + int i; + for (i = 0; i < threads_per_child; i++) { + apr_socket_t *csd = worker_sockets[i]; + if (csd) { + worker_sockets[i] = NULL; + close_socket_nonblocking(csd); + } + } +} + +static void wakeup_listener(void) +{ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "wake up listener%s", listener_may_exit ? " again" : ""); + + listener_may_exit = 1; + disable_listensocks(); + + /* Unblock the listener if it's poll()ing */ + if (event_pollset && listener_is_wakeable) { + apr_pollset_wakeup(event_pollset); + } + + /* unblock the listener if it's waiting for a worker */ + if (worker_queue_info) { + ap_queue_info_term(worker_queue_info); + } + + if (!listener_os_thread) { + /* XXX there is an obscure path that this doesn't handle perfectly: + * right after listener thread is created but before + * listener_os_thread is set, the first worker thread hits an + * error and starts graceful termination + */ + return; + } + /* + * we should just be able to "kill(ap_my_pid, LISTENER_SIGNAL)" on all + * platforms and wake up the listener thread since it is the only thread + * with SIGHUP unblocked, but that doesn't work on Linux + */ +#ifdef HAVE_PTHREAD_KILL + pthread_kill(*listener_os_thread, LISTENER_SIGNAL); +#else + kill(ap_my_pid, LISTENER_SIGNAL); +#endif +} + +#define ST_INIT 0 +#define ST_GRACEFUL 1 +#define ST_UNGRACEFUL 2 + +static int terminate_mode = ST_INIT; + +static void signal_threads(int mode) +{ + if (terminate_mode >= mode) { + return; + } + terminate_mode = mode; + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + /* in case we weren't called from the listener thread, wake up the + * listener thread + */ + wakeup_listener(); + + /* for ungraceful termination, let the workers exit now; + * for graceful termination, the listener thread will notify the + * workers to exit once it has stopped accepting new connections + */ + if (mode == ST_UNGRACEFUL) { + workers_may_exit = 1; + ap_queue_interrupt_all(worker_queue); + close_worker_sockets(); /* forcefully kill all current connections */ + } + + ap_run_child_stopping(pchild, mode == ST_GRACEFUL); +} + +static int event_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = retained->max_daemon_used; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_IS_ASYNC: + *result = 1; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = server_limit; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = threads_per_child; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = min_spare_threads; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = max_spare_threads; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = active_daemons_limit; + break; + case AP_MPMQ_MPM_STATE: + *result = retained->mpm->mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = retained->mpm->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static void event_note_child_stopped(int slot, pid_t pid, ap_generation_t gen) +{ + if (slot != -1) { /* child had a scoreboard slot? */ + process_score *ps = &ap_scoreboard_image->parent[slot]; + int i; + + pid = ps->pid; + gen = ps->generation; + for (i = 0; i < threads_per_child; i++) { + ap_update_child_status_from_indexes(slot, i, SERVER_DEAD, NULL); + } + ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_EXITED); + if (ps->quiescing != 2) { /* vs perform_idle_server_maintenance() */ + retained->active_daemons--; + } + retained->total_daemons--; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d stopped: pid %d, gen %d, " + "active %d/%d, total %d/%d/%d, quiescing %d", + slot, (int)pid, (int)gen, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit, ps->quiescing); + ps->not_accepting = 0; + ps->quiescing = 0; + ps->pid = 0; + } + else { + ap_run_child_status(ap_server_conf, pid, gen, -1, MPM_CHILD_EXITED); + } +} + +static void event_note_child_started(int slot, pid_t pid) +{ + ap_generation_t gen = retained->mpm->my_generation; + + retained->total_daemons++; + retained->active_daemons++; + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = gen; + ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d started: pid %d, gen %d, " + "active %d/%d, total %d/%d/%d", + slot, (int)pid, (int)gen, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit); +} + +static const char *event_get_name(void) +{ + return "event"; +} + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + if (terminate_mode == ST_INIT) { + ap_run_child_stopping(pchild, 0); + } + + if (pchild) { + apr_pool_destroy(pchild); + } + + if (one_process) { + event_note_child_stopped(/* slot */ 0, 0, 0); + } + + exit(code); +} + +static void just_die(int sig) +{ + clean_child_exit(0); +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static int child_fatal; + +static apr_status_t decrement_connection_count(void *cs_) +{ + int is_last_connection; + event_conn_state_t *cs = cs_; + ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, cs->c, + "cleanup connection from state %i", (int)cs->pub.state); + switch (cs->pub.state) { + case CONN_STATE_LINGER: + case CONN_STATE_LINGER_NORMAL: + case CONN_STATE_LINGER_SHORT: + apr_atomic_dec32(&lingering_count); + break; + case CONN_STATE_SUSPENDED: + apr_atomic_dec32(&suspended_count); + break; + default: + break; + } + /* Unblock the listener if it's waiting for connection_count = 0, + * or if the listening sockets were disabled due to limits and can + * now accept new connections. + */ + is_last_connection = !apr_atomic_dec32(&connection_count); + if (listener_is_wakeable + && ((is_last_connection && listener_may_exit) + || should_enable_listensocks())) { + apr_pollset_wakeup(event_pollset); + } + if (dying) { + /* Help worker_thread_should_exit_early() */ + ap_queue_interrupt_one(worker_queue); + } + return APR_SUCCESS; +} + +static void notify_suspend(event_conn_state_t *cs) +{ + ap_run_suspend_connection(cs->c, cs->r); + cs->c->sbh = NULL; + cs->suspended = 1; +} + +static void notify_resume(event_conn_state_t *cs, int cleanup) +{ + cs->suspended = 0; + cs->c->sbh = cleanup ? NULL : cs->sbh; + ap_run_resume_connection(cs->c, cs->r); +} + +/* + * Defer flush and close of the connection by adding it to defer_linger_chain, + * for a worker to grab it and do the job (should that be blocking). + * Pre-condition: nonblocking, can be called from anywhere provided cs is not + * in any timeout queue or in the pollset. + */ +static int defer_lingering_close(event_conn_state_t *cs) +{ + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c, + "deferring close from state %i", (int)cs->pub.state); + + /* The connection is not shutdown() yet strictly speaking, but it's not + * in any queue nor handled by a worker either (will be very soon), so + * to account for it somewhere we bump lingering_count now (and set + * deferred_linger for process_lingering_close() to know). + */ + cs->pub.state = CONN_STATE_LINGER; + apr_atomic_inc32(&lingering_count); + cs->deferred_linger = 1; + for (;;) { + event_conn_state_t *chain = cs->chain = defer_linger_chain; + if (apr_atomic_casptr((void *)&defer_linger_chain, cs, + chain) != chain) { + /* Race lost, try again */ + continue; + } + return 1; + } +} + +/* Close the connection and release its resources (ptrans), either because an + * unrecoverable error occured (queues or pollset add/remove) or more usually + * if lingering close timed out. + * Pre-condition: nonblocking, can be called from anywhere provided cs is not + * in any timeout queue or in the pollset. + */ +static void close_connection(event_conn_state_t *cs) +{ + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c, + "closing connection from state %i", (int)cs->pub.state); + + close_socket_nonblocking(cs->pfd.desc.s); + ap_queue_info_push_pool(worker_queue_info, cs->p); +} + +/* Shutdown the connection in case of timeout, error or resources shortage. + * This starts short lingering close if not already there, or directly closes + * the connection otherwise. + * Pre-condition: nonblocking, can be called from anywhere provided cs is not + * in any timeout queue or in the pollset. + */ +static int shutdown_connection(event_conn_state_t *cs) +{ + if (cs->pub.state < CONN_STATE_LINGER) { + apr_table_setn(cs->c->notes, "short-lingering-close", "1"); + defer_lingering_close(cs); + } + else { + close_connection(cs); + } + return 1; +} + +/* + * This runs before any non-MPM cleanup code on the connection; + * if the connection is currently suspended as far as modules + * know, provide notification of resumption. + */ +static apr_status_t ptrans_pre_cleanup(void *dummy) +{ + event_conn_state_t *cs = dummy; + + if (cs->suspended) { + notify_resume(cs, 1); + } + return APR_SUCCESS; +} + +/* + * event_pre_read_request() and event_request_cleanup() track the + * current r for a given connection. + */ +static apr_status_t event_request_cleanup(void *dummy) +{ + conn_rec *c = dummy; + event_conn_state_t *cs = ap_get_module_config(c->conn_config, + &mpm_event_module); + + cs->r = NULL; + return APR_SUCCESS; +} + +static void event_pre_read_request(request_rec *r, conn_rec *c) +{ + event_conn_state_t *cs = ap_get_module_config(c->conn_config, + &mpm_event_module); + + cs->r = r; + cs->sc = ap_get_module_config(ap_server_conf->module_config, + &mpm_event_module); + apr_pool_cleanup_register(r->pool, c, event_request_cleanup, + apr_pool_cleanup_null); +} + +/* + * event_post_read_request() tracks the current server config for a + * given request. + */ +static int event_post_read_request(request_rec *r) +{ + conn_rec *c = r->connection; + event_conn_state_t *cs = ap_get_module_config(c->conn_config, + &mpm_event_module); + + /* To preserve legacy behaviour (consistent with other MPMs), use + * the keepalive timeout from the base server (first on this IP:port) + * when none is explicitly configured on this server. + */ + if (r->server->keep_alive_timeout_set) { + cs->sc = ap_get_module_config(r->server->module_config, + &mpm_event_module); + } + else { + cs->sc = ap_get_module_config(c->base_server->module_config, + &mpm_event_module); + } + return OK; +} + +/* Forward declare */ +static void process_lingering_close(event_conn_state_t *cs); + +static void update_reqevents_from_sense(event_conn_state_t *cs, int sense) +{ + if (sense < 0) { + sense = cs->pub.sense; + } + if (sense == CONN_SENSE_WANT_READ) { + cs->pfd.reqevents = APR_POLLIN | APR_POLLHUP; + } + else { + cs->pfd.reqevents = APR_POLLOUT; + } + /* POLLERR is usually returned event only, but some pollset + * backends may require it in reqevents to do the right thing, + * so it shouldn't hurt (ignored otherwise). + */ + cs->pfd.reqevents |= APR_POLLERR; + + /* Reset to default for the next round */ + cs->pub.sense = CONN_SENSE_DEFAULT; +} + +/* + * process one connection in the worker + */ +static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock, + event_conn_state_t * cs, int my_child_num, + int my_thread_num) +{ + conn_rec *c; + long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num); + int clogging = 0; + apr_status_t rv; + int rc = OK; + + if (cs == NULL) { /* This is a new connection */ + listener_poll_type *pt = apr_pcalloc(p, sizeof(*pt)); + cs = apr_pcalloc(p, sizeof(event_conn_state_t)); + cs->bucket_alloc = apr_bucket_alloc_create(p); + ap_create_sb_handle(&cs->sbh, p, my_child_num, my_thread_num); + c = ap_run_create_connection(p, ap_server_conf, sock, + conn_id, cs->sbh, cs->bucket_alloc); + if (!c) { + ap_queue_info_push_pool(worker_queue_info, p); + return; + } + apr_atomic_inc32(&connection_count); + apr_pool_cleanup_register(c->pool, cs, decrement_connection_count, + apr_pool_cleanup_null); + ap_set_module_config(c->conn_config, &mpm_event_module, cs); + c->current_thread = thd; + c->cs = &cs->pub; + cs->c = c; + cs->p = p; + cs->sc = ap_get_module_config(ap_server_conf->module_config, + &mpm_event_module); + cs->pfd.desc_type = APR_POLL_SOCKET; + cs->pfd.desc.s = sock; + update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ); + pt->type = PT_CSD; + pt->baton = cs; + cs->pfd.client_data = pt; + apr_pool_pre_cleanup_register(p, cs, ptrans_pre_cleanup); + TO_QUEUE_ELEM_INIT(cs); + + ap_update_vhost_given_ip(c); + + rc = ap_pre_connection(c, sock); + if (rc != OK && rc != DONE) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(00469) + "process_socket: connection aborted"); + } + + /** + * XXX If the platform does not have a usable way of bundling + * accept() with a socket readability check, like Win32, + * and there are measurable delays before the + * socket is readable due to the first data packet arriving, + * it might be better to create the cs on the listener thread + * with the state set to CONN_STATE_CHECK_REQUEST_LINE_READABLE + * + * FreeBSD users will want to enable the HTTP accept filter + * module in their kernel for the highest performance + * When the accept filter is active, sockets are kept in the + * kernel until a HTTP request is received. + */ + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + + cs->pub.sense = CONN_SENSE_DEFAULT; + rc = OK; + } + else { + c = cs->c; + ap_update_sb_handle(cs->sbh, my_child_num, my_thread_num); + notify_resume(cs, 0); + c->current_thread = thd; + /* Subsequent request on a conn, and thread number is part of ID */ + c->id = conn_id; + } + + if (c->aborted) { + /* do lingering close below */ + cs->pub.state = CONN_STATE_LINGER; + } + else if (cs->pub.state >= CONN_STATE_LINGER) { + /* fall through */ + } + else { + if (cs->pub.state == CONN_STATE_READ_REQUEST_LINE + /* If we have an input filter which 'clogs' the input stream, + * like mod_ssl used to, lets just do the normal read from input + * filters, like the Worker MPM does. Filters that need to write + * where they would otherwise read, or read where they would + * otherwise write, should set the sense appropriately. + */ + || c->clogging_input_filters) { +read_request: + clogging = c->clogging_input_filters; + if (clogging) { + apr_atomic_inc32(&clogged_count); + } + rc = ap_run_process_connection(c); + if (clogging) { + apr_atomic_dec32(&clogged_count); + } + if (cs->pub.state > CONN_STATE_LINGER) { + cs->pub.state = CONN_STATE_LINGER; + } + if (rc == DONE) { + rc = OK; + } + } + } + /* + * The process_connection hooks above should set the connection state + * appropriately upon return, for event MPM to either: + * - do lingering close (CONN_STATE_LINGER), + * - wait for readability of the next request with respect to the keepalive + * timeout (state CONN_STATE_CHECK_REQUEST_LINE_READABLE), + * - wait for read/write-ability of the underlying socket with respect to + * its timeout by setting c->clogging_input_filters to 1 and the sense + * to CONN_SENSE_WANT_READ/WRITE (state CONN_STATE_WRITE_COMPLETION), + * - keep flushing the output filters stack in nonblocking mode, and then + * if required wait for read/write-ability of the underlying socket with + * respect to its own timeout (state CONN_STATE_WRITE_COMPLETION); since + * completion at some point may require reads (e.g. SSL_ERROR_WANT_READ), + * an output filter can also set the sense to CONN_SENSE_WANT_READ at any + * time for event MPM to do the right thing, + * - suspend the connection (SUSPENDED) such that it now interacts with + * the MPM through suspend/resume_connection() hooks, and/or registered + * poll callbacks (PT_USER), and/or registered timed callbacks triggered + * by timer events. + * If a process_connection hook returns an error or no hook sets the state + * to one of the above expected value, we forcibly close the connection w/ + * CONN_STATE_LINGER. This covers the cases where no process_connection + * hook executes (DECLINED), or one returns OK w/o touching the state (i.e. + * CONN_STATE_READ_REQUEST_LINE remains after the call) which can happen + * with third-party modules not updated to work specifically with event MPM + * while this was expected to do lingering close unconditionally with + * worker or prefork MPMs for instance. + */ + if (rc != OK || (cs->pub.state >= CONN_STATE_NUM) + || (cs->pub.state < CONN_STATE_LINGER + && cs->pub.state != CONN_STATE_WRITE_COMPLETION + && cs->pub.state != CONN_STATE_CHECK_REQUEST_LINE_READABLE + && cs->pub.state != CONN_STATE_SUSPENDED)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10111) + "process_socket: connection processing %s: closing", + rc ? apr_psprintf(c->pool, "returned error %i", rc) + : apr_psprintf(c->pool, "unexpected state %i", + (int)cs->pub.state)); + cs->pub.state = CONN_STATE_LINGER; + } + + if (cs->pub.state == CONN_STATE_WRITE_COMPLETION) { + ap_filter_t *output_filter = c->output_filters; + apr_status_t rv; + ap_update_child_status(cs->sbh, SERVER_BUSY_WRITE, NULL); + while (output_filter->next != NULL) { + output_filter = output_filter->next; + } + rv = output_filter->frec->filter_func.out_func(output_filter, NULL); + if (rv != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00470) + "network write failure in core output filter"); + cs->pub.state = CONN_STATE_LINGER; + } + else if (c->data_in_output_filters || + cs->pub.sense == CONN_SENSE_WANT_READ) { + /* Still in WRITE_COMPLETION_STATE: + * Set a read/write timeout for this connection, and let the + * event thread poll for read/writeability. + */ + cs->queue_timestamp = apr_time_now(); + notify_suspend(cs); + + update_reqevents_from_sense(cs, -1); + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(cs->sc->wc_q, cs); + rv = apr_pollset_add(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + AP_DEBUG_ASSERT(0); + TO_QUEUE_REMOVE(cs->sc->wc_q, cs); + apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03465) + "process_socket: apr_pollset_add failure for " + "write completion"); + close_connection(cs); + signal_threads(ST_GRACEFUL); + } + else { + apr_thread_mutex_unlock(timeout_mutex); + } + return; + } + else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted) { + cs->pub.state = CONN_STATE_LINGER; + } + else if (c->data_in_input_filters) { + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + goto read_request; + } + else if (!listener_may_exit) { + cs->pub.state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; + } + else { + cs->pub.state = CONN_STATE_LINGER; + } + } + + if (cs->pub.state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { + ap_update_child_status(cs->sbh, SERVER_BUSY_KEEPALIVE, NULL); + + /* It greatly simplifies the logic to use a single timeout value per q + * because the new element can just be added to the end of the list and + * it will stay sorted in expiration time sequence. If brand new + * sockets are sent to the event thread for a readability check, this + * will be a slight behavior change - they use the non-keepalive + * timeout today. With a normal client, the socket will be readable in + * a few milliseconds anyway. + */ + cs->queue_timestamp = apr_time_now(); + notify_suspend(cs); + + /* Add work to pollset. */ + update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ); + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(cs->sc->ka_q, cs); + rv = apr_pollset_add(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + AP_DEBUG_ASSERT(0); + TO_QUEUE_REMOVE(cs->sc->ka_q, cs); + apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03093) + "process_socket: apr_pollset_add failure for " + "keep alive"); + close_connection(cs); + signal_threads(ST_GRACEFUL); + } + else { + apr_thread_mutex_unlock(timeout_mutex); + } + return; + } + + if (cs->pub.state == CONN_STATE_SUSPENDED) { + apr_atomic_inc32(&suspended_count); + notify_suspend(cs); + return; + } + + /* CONN_STATE_LINGER[_*] fall through process_lingering_close() */ + if (cs->pub.state >= CONN_STATE_LINGER) { + process_lingering_close(cs); + return; + } +} + +/* conns_this_child has gone to zero or below. See if the admin coded + "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) +{ + if (ap_max_requests_per_child) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "Stopping process due to MaxConnectionsPerChild"); + signal_threads(ST_GRACEFUL); + } + else { + /* keep going */ + conns_this_child = APR_INT32_MAX; + } +} + +static int close_listeners(int *closed) +{ + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf, + "clos%s listeners (connection_count=%u)", + *closed ? "ed" : "ing", apr_atomic_read32(&connection_count)); + if (!*closed) { + int i; + + ap_close_listeners_ex(my_bucket->listeners); + *closed = 1; /* once */ + + dying = 1; + ap_scoreboard_image->parent[ap_child_slot].quiescing = 1; + for (i = 0; i < threads_per_child; ++i) { + ap_update_child_status_from_indexes(ap_child_slot, i, + SERVER_GRACEFUL, NULL); + } + /* wake up the main thread */ + kill(ap_my_pid, SIGTERM); + + ap_queue_info_free_idle_pools(worker_queue_info); + ap_queue_interrupt_all(worker_queue); + + return 1; + } + return 0; +} + +static void unblock_signal(int sig) +{ + sigset_t sig_mask; + + sigemptyset(&sig_mask); + sigaddset(&sig_mask, sig); +#if defined(SIGPROCMASK_SETS_THREAD_MASK) + sigprocmask(SIG_UNBLOCK, &sig_mask, NULL); +#else + pthread_sigmask(SIG_UNBLOCK, &sig_mask, NULL); +#endif +} + +static void dummy_signal_handler(int sig) +{ + /* XXX If specifying SIG_IGN is guaranteed to unblock a syscall, + * then we don't need this goofy function. + */ +} + + +static apr_status_t push_timer2worker(timer_event_t* te) +{ + return ap_queue_push_timer(worker_queue, te); +} + +/* + * Pre-condition: cs is neither in event_pollset nor a timeout queue + * this function may only be called by the listener + */ +static apr_status_t push2worker(event_conn_state_t *cs, apr_socket_t *csd, + apr_pool_t *ptrans) +{ + apr_status_t rc; + + if (cs) { + csd = cs->pfd.desc.s; + ptrans = cs->p; + } + rc = ap_queue_push_socket(worker_queue, csd, cs, ptrans); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, ap_server_conf, APLOGNO(00471) + "push2worker: ap_queue_push_socket failed"); + /* trash the connection; we couldn't queue the connected + * socket to a worker + */ + if (cs) { + shutdown_connection(cs); + } + else { + if (csd) { + close_socket_nonblocking(csd); + } + if (ptrans) { + ap_queue_info_push_pool(worker_queue_info, ptrans); + } + } + signal_threads(ST_GRACEFUL); + } + + return rc; +} + +/* get_worker: + * If *have_idle_worker_p == 0, reserve a worker thread, and set + * *have_idle_worker_p = 1. + * If *have_idle_worker_p is already 1, will do nothing. + * If blocking == 1, block if all workers are currently busy. + * If no worker was available immediately, will set *all_busy to 1. + * XXX: If there are no workers, we should not block immediately but + * XXX: close all keep-alive connections first. + */ +static void get_worker(int *have_idle_worker_p, int blocking, int *all_busy) +{ + apr_status_t rc; + + if (*have_idle_worker_p) { + /* already reserved a worker thread - must have hit a + * transient error on a previous pass + */ + return; + } + + if (blocking) + rc = ap_queue_info_wait_for_idler(worker_queue_info, all_busy); + else + rc = ap_queue_info_try_get_idler(worker_queue_info); + + if (rc == APR_SUCCESS || APR_STATUS_IS_EOF(rc)) { + *have_idle_worker_p = 1; + } + else if (!blocking && rc == APR_EAGAIN) { + *all_busy = 1; + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, APLOGNO(00472) + "ap_queue_info_wait_for_idler failed. " + "Attempting to shutdown process gracefully"); + signal_threads(ST_GRACEFUL); + } +} + +/* Structures to reuse */ +static timer_event_t timer_free_ring; + +static apr_skiplist *timer_skiplist; +static volatile apr_time_t timers_next_expiry; + +/* Same goal as for TIMEOUT_FUDGE_FACTOR (avoid extra poll calls), but applied + * to timers. Since their timeouts are custom (user defined), we can't be too + * approximative here (hence using 0.01s). + */ +#define EVENT_FUDGE_FACTOR apr_time_from_msec(10) + +/* The following compare function is used by apr_skiplist_insert() to keep the + * elements (timers) sorted and provide O(log n) complexity (this is also true + * for apr_skiplist_{find,remove}(), but those are not used in MPM event where + * inserted timers are not searched nor removed, but with apr_skiplist_pop() + * which does use any compare function). It is meant to return 0 when a == b, + * <0 when a < b, and >0 when a > b. However apr_skiplist_insert() will not + * add duplicates (i.e. a == b), and apr_skiplist_add() is only available in + * APR 1.6, yet multiple timers could possibly be created in the same micro- + * second (duplicates with regard to apr_time_t); therefore we implement the + * compare function to return +1 instead of 0 when compared timers are equal, + * thus duplicates are still added after each other (in order of insertion). + */ +static int timer_comp(void *a, void *b) +{ + apr_time_t t1 = (apr_time_t) ((timer_event_t *)a)->when; + apr_time_t t2 = (apr_time_t) ((timer_event_t *)b)->when; + AP_DEBUG_ASSERT(t1); + AP_DEBUG_ASSERT(t2); + return ((t1 < t2) ? -1 : 1); +} + +static apr_thread_mutex_t *g_timer_skiplist_mtx; + +static apr_status_t event_register_timed_callback(apr_time_t t, + ap_mpm_callback_fn_t *cbfn, + void *baton) +{ + timer_event_t *te; + /* oh yeah, and make locking smarter/fine grained. */ + apr_thread_mutex_lock(g_timer_skiplist_mtx); + + if (!APR_RING_EMPTY(&timer_free_ring.link, timer_event_t, link)) { + te = APR_RING_FIRST(&timer_free_ring.link); + APR_RING_REMOVE(te, link); + } + else { + te = apr_skiplist_alloc(timer_skiplist, sizeof(timer_event_t)); + APR_RING_ELEM_INIT(te, link); + } + + te->cbfunc = cbfn; + te->baton = baton; + /* XXXXX: optimize */ + te->when = t + apr_time_now(); + + { + apr_time_t next_expiry; + + /* Okay, add sorted by when.. */ + apr_skiplist_insert(timer_skiplist, te); + + /* Cheaply update the global timers_next_expiry with this event's + * if it expires before. + */ + next_expiry = timers_next_expiry; + if (!next_expiry || next_expiry > te->when + EVENT_FUDGE_FACTOR) { + timers_next_expiry = te->when; + /* Unblock the poll()ing listener for it to update its timeout. */ + if (listener_is_wakeable) { + apr_pollset_wakeup(event_pollset); + } + } + } + + apr_thread_mutex_unlock(g_timer_skiplist_mtx); + + return APR_SUCCESS; +} + + +/* + * Flush data and close our side of the connection, then drain incoming data. + * If the latter would block put the connection in one of the linger timeout + * queues to be called back when ready, and repeat until it's closed by peer. + * Only to be called in the worker thread, and since it's in immediate call + * stack, we can afford a comfortable buffer size to consume data quickly. + * Pre-condition: cs is not in any timeout queue and not in the pollset, + * timeout_mutex is not locked + */ +#define LINGERING_BUF_SIZE (32 * 1024) +static void process_lingering_close(event_conn_state_t *cs) +{ + apr_socket_t *csd = ap_get_conn_socket(cs->c); + char dummybuf[LINGERING_BUF_SIZE]; + apr_size_t nbytes; + apr_status_t rv; + struct timeout_queue *q; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c, + "lingering close from state %i", (int)cs->pub.state); + AP_DEBUG_ASSERT(cs->pub.state >= CONN_STATE_LINGER); + + if (cs->pub.state == CONN_STATE_LINGER) { + /* defer_lingering_close() may have bumped lingering_count already */ + if (!cs->deferred_linger) { + apr_atomic_inc32(&lingering_count); + } + + apr_socket_timeout_set(csd, apr_time_from_sec(SECONDS_TO_LINGER)); + if (ap_start_lingering_close(cs->c)) { + notify_suspend(cs); + close_connection(cs); + return; + } + + cs->queue_timestamp = apr_time_now(); + /* Clear APR_INCOMPLETE_READ if it was ever set, we'll do the poll() + * at the listener only from now, if needed. + */ + apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 0); + /* + * If some module requested a shortened waiting period, only wait for + * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain + * DoS attacks. + */ + if (apr_table_get(cs->c->notes, "short-lingering-close")) { + cs->pub.state = CONN_STATE_LINGER_SHORT; + } + else { + cs->pub.state = CONN_STATE_LINGER_NORMAL; + } + notify_suspend(cs); + } + + apr_socket_timeout_set(csd, 0); + do { + nbytes = sizeof(dummybuf); + rv = apr_socket_recv(csd, dummybuf, &nbytes); + } while (rv == APR_SUCCESS); + + if (!APR_STATUS_IS_EAGAIN(rv)) { + close_connection(cs); + return; + } + + /* (Re)queue the connection to come back when readable */ + update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ); + q = (cs->pub.state == CONN_STATE_LINGER_SHORT) ? short_linger_q : linger_q; + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(q, cs); + rv = apr_pollset_add(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + AP_DEBUG_ASSERT(0); + TO_QUEUE_REMOVE(q, cs); + apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03092) + "process_lingering_close: apr_pollset_add failure"); + close_connection(cs); + signal_threads(ST_GRACEFUL); + return; + } + apr_thread_mutex_unlock(timeout_mutex); +} + +/* call 'func' for all elements of 'q' above 'expiry'. + * Pre-condition: timeout_mutex must already be locked + * Post-condition: timeout_mutex will be locked again + */ +static void process_timeout_queue(struct timeout_queue *q, apr_time_t expiry, + int (*func)(event_conn_state_t *)) +{ + apr_uint32_t total = 0, count; + event_conn_state_t *first, *cs, *last; + struct event_conn_state_t trash; + struct timeout_queue *qp; + apr_status_t rv; + + if (!*q->total) { + return; + } + + APR_RING_INIT(&trash.timeout_list, event_conn_state_t, timeout_list); + for (qp = q; qp; qp = qp->next) { + count = 0; + cs = first = last = APR_RING_FIRST(&qp->head); + while (cs != APR_RING_SENTINEL(&qp->head, event_conn_state_t, + timeout_list)) { + /* Trash the entry if: + * - no expiry was given (zero means all), or + * - it expired (according to the queue timeout), or + * - the system clock skewed in the past: no entry should be + * registered above the given expiry (~now) + the queue + * timeout, we won't keep any here (eg. for centuries). + * + * Otherwise stop, no following entry will match thanks to the + * single timeout per queue (entries are added to the end!). + * This allows maintenance in O(1). + */ + if (expiry && cs->queue_timestamp + qp->timeout > expiry + && cs->queue_timestamp < expiry + qp->timeout) { + /* Since this is the next expiring entry of this queue, update + * the global queues_next_expiry if it's later than this one. + */ + apr_time_t elem_expiry = cs->queue_timestamp + qp->timeout; + apr_time_t next_expiry = queues_next_expiry; + if (!next_expiry + || next_expiry > elem_expiry + TIMEOUT_FUDGE_FACTOR) { + queues_next_expiry = elem_expiry; + } + break; + } + + last = cs; + rv = apr_pollset_remove(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_NOTFOUND(rv)) { + AP_DEBUG_ASSERT(0); + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, cs->c, APLOGNO(00473) + "apr_pollset_remove failed"); + } + cs = APR_RING_NEXT(cs, timeout_list); + count++; + } + if (!count) + continue; + + APR_RING_UNSPLICE(first, last, timeout_list); + APR_RING_SPLICE_TAIL(&trash.timeout_list, first, last, event_conn_state_t, + timeout_list); + AP_DEBUG_ASSERT(*q->total >= count && qp->count >= count); + *q->total -= count; + qp->count -= count; + total += count; + } + if (!total) + return; + + apr_thread_mutex_unlock(timeout_mutex); + first = APR_RING_FIRST(&trash.timeout_list); + do { + cs = APR_RING_NEXT(first, timeout_list); + TO_QUEUE_ELEM_INIT(first); + func(first); + first = cs; + } while (--total); + apr_thread_mutex_lock(timeout_mutex); +} + +static void process_keepalive_queue(apr_time_t expiry) +{ + /* If all workers are busy, we kill older keep-alive connections so + * that they may connect to another process. + */ + if (!expiry && *keepalive_q->total) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "All workers are busy or dying, will shutdown %u " + "keep-alive connections", *keepalive_q->total); + } + process_timeout_queue(keepalive_q, expiry, shutdown_connection); +} + +static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) +{ + apr_status_t rc; + proc_info *ti = dummy; + int process_slot = ti->pslot; + struct process_score *ps = ap_get_scoreboard_process(process_slot); + int closed = 0; + int have_idle_worker = 0; + apr_time_t last_log; + + last_log = apr_time_now(); + free(ti); + + /* Unblock the signal used to wake this thread up, and set a handler for + * it. + */ + apr_signal(LISTENER_SIGNAL, dummy_signal_handler); + unblock_signal(LISTENER_SIGNAL); + + for (;;) { + timer_event_t *te; + const apr_pollfd_t *out_pfd; + apr_int32_t num = 0; + apr_interval_time_t timeout; + apr_time_t now, expiry = -1; + int workers_were_busy = 0; + + if (conns_this_child <= 0) + check_infinite_requests(); + + if (listener_may_exit) { + int first_close = close_listeners(&closed); + + if (terminate_mode == ST_UNGRACEFUL + || apr_atomic_read32(&connection_count) == 0) + break; + + /* Don't wait in poll() for the first close (i.e. dying now), we + * want to maintain the queues and schedule defer_linger_chain ASAP + * to kill kept-alive connection and shutdown the workers and child + * faster. + */ + if (first_close) { + goto do_maintenance; /* with expiry == -1 */ + } + } + + now = apr_time_now(); + if (APLOGtrace6(ap_server_conf)) { + /* trace log status every second */ + if (now - last_log > apr_time_from_sec(1)) { + last_log = now; + apr_thread_mutex_lock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf, + "connections: %u (clogged: %u write-completion: %d " + "keep-alive: %d lingering: %d suspended: %u)", + apr_atomic_read32(&connection_count), + apr_atomic_read32(&clogged_count), + apr_atomic_read32(write_completion_q->total), + apr_atomic_read32(keepalive_q->total), + apr_atomic_read32(&lingering_count), + apr_atomic_read32(&suspended_count)); + if (dying) { + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf, + "%u/%u workers shutdown", + apr_atomic_read32(&threads_shutdown), + threads_per_child); + } + apr_thread_mutex_unlock(timeout_mutex); + } + } + + /* Start with an infinite poll() timeout and update it according to + * the next expiring timer or queue entry. If there are none, either + * the listener is wakeable and it can poll() indefinitely until a wake + * up occurs, otherwise periodic checks (maintenance, shutdown, ...) + * must be performed. + */ + now = apr_time_now(); + timeout = -1; + + /* Push expired timers to a worker, the first remaining one determines + * the maximum time to poll() below, if any. + */ + expiry = timers_next_expiry; + if (expiry && expiry < now) { + apr_thread_mutex_lock(g_timer_skiplist_mtx); + while ((te = apr_skiplist_peek(timer_skiplist))) { + if (te->when > now) { + timers_next_expiry = te->when; + timeout = te->when - now; + break; + } + apr_skiplist_pop(timer_skiplist, NULL); + push_timer2worker(te); + } + if (!te) { + timers_next_expiry = 0; + } + apr_thread_mutex_unlock(g_timer_skiplist_mtx); + } + + /* Same for queues, use their next expiry, if any. */ + expiry = queues_next_expiry; + if (expiry + && (timeout < 0 + || expiry <= now + || timeout > expiry - now)) { + timeout = expiry > now ? expiry - now : 0; + } + + /* When non-wakeable, don't wait more than 100 ms, in any case. */ +#define NON_WAKEABLE_POLL_TIMEOUT apr_time_from_msec(100) + if (!listener_is_wakeable + && (timeout < 0 + || timeout > NON_WAKEABLE_POLL_TIMEOUT)) { + timeout = NON_WAKEABLE_POLL_TIMEOUT; + } + else if (timeout > 0) { + /* apr_pollset_poll() might round down the timeout to milliseconds, + * let's forcibly round up here to never return before the timeout. + */ + timeout = apr_time_from_msec( + apr_time_as_msec(timeout + apr_time_from_msec(1) - 1) + ); + } + + ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf, + "polling with timeout=%" APR_TIME_T_FMT + " queues_timeout=%" APR_TIME_T_FMT + " timers_timeout=%" APR_TIME_T_FMT, + timeout, queues_next_expiry - now, + timers_next_expiry - now); + + rc = apr_pollset_poll(event_pollset, timeout, &num, &out_pfd); + if (rc != APR_SUCCESS) { + if (!APR_STATUS_IS_EINTR(rc) && !APR_STATUS_IS_TIMEUP(rc)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, ap_server_conf, + "apr_pollset_poll failed. Attempting to " + "shutdown process gracefully"); + signal_threads(ST_GRACEFUL); + } + num = 0; + } + + if (APLOGtrace7(ap_server_conf)) { + now = apr_time_now(); + ap_log_error(APLOG_MARK, APLOG_TRACE7, rc, ap_server_conf, + "polled with num=%u exit=%d/%d conns=%d" + " queues_timeout=%" APR_TIME_T_FMT + " timers_timeout=%" APR_TIME_T_FMT, + num, listener_may_exit, dying, + apr_atomic_read32(&connection_count), + queues_next_expiry - now, timers_next_expiry - now); + } + + /* XXX possible optimization: stash the current time for use as + * r->request_time for new requests or queues maintenance + */ + + for (; num; --num, ++out_pfd) { + listener_poll_type *pt = (listener_poll_type *) out_pfd->client_data; + if (pt->type == PT_CSD) { + /* one of the sockets is readable */ + event_conn_state_t *cs = (event_conn_state_t *) pt->baton; + struct timeout_queue *remove_from_q = NULL; + /* don't wait for a worker for a keepalive request or + * lingering close processing. */ + int blocking = 0; + + switch (cs->pub.state) { + case CONN_STATE_WRITE_COMPLETION: + remove_from_q = cs->sc->wc_q; + blocking = 1; + break; + + case CONN_STATE_CHECK_REQUEST_LINE_READABLE: + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + remove_from_q = cs->sc->ka_q; + break; + + case CONN_STATE_LINGER_NORMAL: + remove_from_q = linger_q; + break; + + case CONN_STATE_LINGER_SHORT: + remove_from_q = short_linger_q; + break; + + default: + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, + ap_server_conf, APLOGNO(03096) + "event_loop: unexpected state %d", + cs->pub.state); + ap_assert(0); + } + + if (remove_from_q) { + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_REMOVE(remove_from_q, cs); + rc = apr_pollset_remove(event_pollset, &cs->pfd); + apr_thread_mutex_unlock(timeout_mutex); + /* + * Some of the pollset backends, like KQueue or Epoll + * automagically remove the FD if the socket is closed, + * therefore, we can accept _SUCCESS or _NOTFOUND, + * and we still want to keep going + */ + if (rc != APR_SUCCESS && !APR_STATUS_IS_NOTFOUND(rc)) { + AP_DEBUG_ASSERT(0); + ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, + APLOGNO(03094) "pollset remove failed"); + close_connection(cs); + signal_threads(ST_GRACEFUL); + break; + } + + /* If we don't get a worker immediately (nonblocking), we + * close the connection; the client can re-connect to a + * different process for keepalive, and for lingering close + * the connection will be shutdown so the choice is to favor + * incoming/alive connections. + */ + get_worker(&have_idle_worker, blocking, + &workers_were_busy); + if (!have_idle_worker) { + shutdown_connection(cs); + } + else if (push2worker(cs, NULL, NULL) == APR_SUCCESS) { + have_idle_worker = 0; + } + } + } + else if (pt->type == PT_ACCEPT && !listeners_disabled()) { + /* A Listener Socket is ready for an accept() */ + if (workers_were_busy) { + disable_listensocks(); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "All workers busy, not accepting new conns " + "in this process"); + } + else if (connections_above_limit(&workers_were_busy)) { + disable_listensocks(); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Too many open connections (%u), " + "not accepting new conns in this process", + apr_atomic_read32(&connection_count)); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "Idle workers: %u", + ap_queue_info_num_idlers(worker_queue_info)); + } + else if (!listener_may_exit) { + void *csd = NULL; + ap_listen_rec *lr = (ap_listen_rec *) pt->baton; + apr_pool_t *ptrans; /* Pool for per-transaction stuff */ + ap_queue_info_pop_pool(worker_queue_info, &ptrans); + + if (ptrans == NULL) { + /* create a new transaction pool for each accepted socket */ + apr_allocator_t *allocator = NULL; + + rc = apr_allocator_create(&allocator); + if (rc == APR_SUCCESS) { + apr_allocator_max_free_set(allocator, + ap_max_mem_free); + rc = apr_pool_create_ex(&ptrans, pconf, NULL, + allocator); + if (rc == APR_SUCCESS) { + apr_pool_tag(ptrans, "transaction"); + apr_allocator_owner_set(allocator, ptrans); + } + } + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, + ap_server_conf, APLOGNO(03097) + "Failed to create transaction pool"); + if (allocator) { + apr_allocator_destroy(allocator); + } + resource_shortage = 1; + signal_threads(ST_GRACEFUL); + continue; + } + } + + get_worker(&have_idle_worker, 1, &workers_were_busy); + rc = lr->accept_func(&csd, lr, ptrans); + + /* later we trash rv and rely on csd to indicate + * success/failure + */ + AP_DEBUG_ASSERT(rc == APR_SUCCESS || !csd); + + if (rc == APR_EGENERAL) { + /* E[NM]FILE, ENOMEM, etc */ + resource_shortage = 1; + signal_threads(ST_GRACEFUL); + } + + if (csd != NULL) { + conns_this_child--; + if (push2worker(NULL, csd, ptrans) == APR_SUCCESS) { + have_idle_worker = 0; + } + } + else { + ap_queue_info_push_pool(worker_queue_info, ptrans); + } + } + } /* if:else on pt->type */ + } /* for processing poll */ + + /* We process the timeout queues here only when the global + * queues_next_expiry is passed. This happens accurately since + * adding to the queues (in workers) can only decrease this expiry, + * while latest ones are only taken into account here (in listener) + * during queues' processing, with the lock held. This works both + * with and without wake-ability. + */ + expiry = queues_next_expiry; +do_maintenance: + if (expiry && expiry < (now = apr_time_now())) { + ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf, + "queues maintenance with timeout=%" APR_TIME_T_FMT, + expiry > 0 ? expiry - now : -1); + apr_thread_mutex_lock(timeout_mutex); + + /* Steps below will recompute this. */ + queues_next_expiry = 0; + + /* Step 1: keepalive timeouts */ + if (workers_were_busy || dying) { + process_keepalive_queue(0); /* kill'em all \m/ */ + } + else { + process_keepalive_queue(now); + } + /* Step 2: write completion timeouts */ + process_timeout_queue(write_completion_q, now, + defer_lingering_close); + /* Step 3: (normal) lingering close completion timeouts */ + if (dying && linger_q->timeout > short_linger_q->timeout) { + /* Dying, force short timeout for normal lingering close */ + linger_q->timeout = short_linger_q->timeout; + } + process_timeout_queue(linger_q, now, shutdown_connection); + /* Step 4: (short) lingering close completion timeouts */ + process_timeout_queue(short_linger_q, now, shutdown_connection); + + apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf, + "queues maintained with timeout=%" APR_TIME_T_FMT, + queues_next_expiry > now ? queues_next_expiry - now + : -1); + + ps->keep_alive = apr_atomic_read32(keepalive_q->total); + ps->write_completion = apr_atomic_read32(write_completion_q->total); + ps->connections = apr_atomic_read32(&connection_count); + ps->suspended = apr_atomic_read32(&suspended_count); + ps->lingering_close = apr_atomic_read32(&lingering_count); + } + else if ((workers_were_busy || dying) + && apr_atomic_read32(keepalive_q->total)) { + apr_thread_mutex_lock(timeout_mutex); + process_keepalive_queue(0); /* kill'em all \m/ */ + apr_thread_mutex_unlock(timeout_mutex); + ps->keep_alive = 0; + } + + /* If there are some lingering closes to defer (to a worker), schedule + * them now. We might wakeup a worker spuriously if another one empties + * defer_linger_chain in the meantime, but there also may be no active + * or all busy workers for an undefined time. In any case a deferred + * lingering close can't starve if we do that here since the chain is + * filled only above in the listener and it's emptied only in the + * worker(s); thus a NULL here means it will stay so while the listener + * waits (possibly indefinitely) in poll(). + */ + if (defer_linger_chain) { + get_worker(&have_idle_worker, 0, &workers_were_busy); + if (have_idle_worker + && defer_linger_chain /* re-test */ + && push2worker(NULL, NULL, NULL) == APR_SUCCESS) { + have_idle_worker = 0; + } + } + + if (!workers_were_busy && should_enable_listensocks()) { + enable_listensocks(); + } + } /* listener main loop */ + + ap_queue_term(worker_queue); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +/* + * During graceful shutdown, if there are more running worker threads than + * open connections, exit one worker thread. + * + * return 1 if thread should exit, 0 if it should continue running. + */ +static int worker_thread_should_exit_early(void) +{ + for (;;) { + apr_uint32_t conns = apr_atomic_read32(&connection_count); + apr_uint32_t dead = apr_atomic_read32(&threads_shutdown); + apr_uint32_t newdead; + + AP_DEBUG_ASSERT(dead <= threads_per_child); + if (conns >= threads_per_child - dead) + return 0; + + newdead = dead + 1; + if (apr_atomic_cas32(&threads_shutdown, newdead, dead) == dead) { + /* + * No other thread has exited in the mean time, safe to exit + * this one. + */ + return 1; + } + } +} + +/* XXX For ungraceful termination/restart, we definitely don't want to + * wait for active connections to finish but we may want to wait + * for idle workers to get out of the queue code and release mutexes, + * since those mutexes are cleaned up pretty soon and some systems + * may not react favorably (i.e., segfault) if operations are attempted + * on cleaned-up mutexes. + */ +static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) +{ + proc_info *ti = dummy; + int process_slot = ti->pslot; + int thread_slot = ti->tslot; + apr_status_t rv; + int is_idle = 0; + + free(ti); + + 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 = retained->mpm->my_generation; + ap_update_child_status_from_indexes(process_slot, thread_slot, + SERVER_STARTING, NULL); + + for (;;) { + apr_socket_t *csd = NULL; + event_conn_state_t *cs; + timer_event_t *te = NULL; + apr_pool_t *ptrans; /* Pool for per-transaction stuff */ + + if (!is_idle) { + rv = ap_queue_info_set_idle(worker_queue_info, NULL); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "ap_queue_info_set_idle failed. Attempting to " + "shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + break; + } + /* A new idler may have changed connections_above_limit(), + * let the listener know and decide. + */ + if (listener_is_wakeable && should_enable_listensocks()) { + apr_pollset_wakeup(event_pollset); + } + is_idle = 1; + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + dying ? SERVER_GRACEFUL + : SERVER_READY, NULL); + worker_pop: + if (workers_may_exit) { + break; + } + if (dying && worker_thread_should_exit_early()) { + break; + } + + rv = ap_queue_pop_something(worker_queue, &csd, (void **)&cs, + &ptrans, &te); + + if (rv != APR_SUCCESS) { + /* We get APR_EOF during a graceful shutdown once all the + * connections accepted by this server process have been handled. + */ + if (APR_STATUS_IS_EOF(rv)) { + break; + } + /* We get APR_EINTR whenever ap_queue_pop_*() has been interrupted + * from an explicit call to ap_queue_interrupt_all(). This allows + * us to unblock threads stuck in ap_queue_pop_*() when a shutdown + * is pending. + * + * If workers_may_exit is set and this is ungraceful termination/ + * restart, we are bound to get an error on some systems (e.g., + * AIX, which sanity-checks mutex operations) since the queue + * may have already been cleaned up. Don't log the "error" if + * workers_may_exit is set. + */ + else if (APR_STATUS_IS_EINTR(rv)) { + goto worker_pop; + } + /* We got some other error. */ + else if (!workers_may_exit) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + APLOGNO(03099) "ap_queue_pop_socket failed"); + } + continue; + } + if (te != NULL) { + te->cbfunc(te->baton); + + { + apr_thread_mutex_lock(g_timer_skiplist_mtx); + APR_RING_INSERT_TAIL(&timer_free_ring.link, te, timer_event_t, link); + apr_thread_mutex_unlock(g_timer_skiplist_mtx); + } + } + else { + is_idle = 0; + if (csd != NULL) { + worker_sockets[thread_slot] = csd; + process_socket(thd, ptrans, csd, cs, process_slot, thread_slot); + worker_sockets[thread_slot] = NULL; + } + } + + /* If there are deferred lingering closes, handle them now. */ + while (!workers_may_exit) { + cs = defer_linger_chain; + if (!cs) { + break; + } + if (apr_atomic_casptr((void *)&defer_linger_chain, cs->chain, + cs) != cs) { + /* Race lost, try again */ + continue; + } + cs->chain = NULL; + AP_DEBUG_ASSERT(cs->pub.state == CONN_STATE_LINGER); + + worker_sockets[thread_slot] = csd = cs->pfd.desc.s; + process_socket(thd, cs->p, csd, cs, process_slot, thread_slot); + worker_sockets[thread_slot] = NULL; + } + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + dying ? SERVER_DEAD + : SERVER_GRACEFUL, NULL); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static int check_signal(int signum) +{ + switch (signum) { + case SIGTERM: + case SIGINT: + return 1; + } + return 0; +} + +static void create_listener_thread(thread_starter * ts) +{ + int my_child_num = ts->child_num_arg; + apr_threadattr_t *thread_attr = ts->threadattr; + proc_info *my_info; + apr_status_t rv; + + my_info = (proc_info *) ap_malloc(sizeof(proc_info)); + my_info->pslot = my_child_num; + my_info->tslot = -1; /* listener thread doesn't have a thread slot */ + rv = ap_thread_create(&ts->listener, thread_attr, listener_thread, + my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00474) + "ap_thread_create: unable to create listener thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + apr_os_thread_get(&listener_os_thread, ts->listener); +} + +static void setup_threads_runtime(void) +{ + apr_status_t rv; + ap_listen_rec *lr; + apr_pool_t *pskip = NULL; + int max_recycled_pools = -1, i; + const int good_methods[] = { APR_POLLSET_KQUEUE, + APR_POLLSET_PORT, + APR_POLLSET_EPOLL }; + /* XXX: K-A or lingering close connection included in the async factor */ + const apr_uint32_t async_factor = worker_factor / WORKER_FACTOR_SCALE; + const apr_uint32_t pollset_size = (apr_uint32_t)num_listensocks + + (apr_uint32_t)threads_per_child * + (async_factor > 2 ? async_factor : 2); + int pollset_flags; + + /* Event's skiplist operations will happen concurrently with other modules' + * runtime so they need their own pool for allocations, and its lifetime + * should be at least the one of the connections (ptrans). Thus pskip is + * created as a subpool of pconf like/before ptrans (before so that it's + * destroyed after). In forked mode pconf is never destroyed so we are good + * anyway, but in ONE_PROCESS mode this ensures that the skiplist works + * from connection/ptrans cleanups (even after pchild is destroyed). + */ + apr_pool_create(&pskip, pconf); + apr_pool_tag(pskip, "mpm_skiplist"); + apr_thread_mutex_create(&g_timer_skiplist_mtx, APR_THREAD_MUTEX_DEFAULT, pskip); + APR_RING_INIT(&timer_free_ring.link, timer_event_t, link); + apr_skiplist_init(&timer_skiplist, pskip); + apr_skiplist_set_compare(timer_skiplist, timer_comp, timer_comp); + + /* All threads (listener, workers) and synchronization objects (queues, + * pollset, mutexes...) created here should have at least the lifetime of + * the connections they handle (i.e. ptrans). We can't use this thread's + * self pool because all these objects survive it, nor use pchild or pconf + * directly because this starter thread races with other modules' runtime, + * nor finally pchild (or subpool thereof) because it is killed explicitly + * before pconf (thus connections/ptrans can live longer, which matters in + * ONE_PROCESS mode). So this leaves us with a subpool of pconf, created + * before any ptrans hence destroyed after. + */ + apr_pool_create(&pruntime, pconf); + apr_pool_tag(pruntime, "mpm_runtime"); + + /* We must create the fd queues before we start up the listener + * and worker threads. */ + rv = ap_queue_create(&worker_queue, threads_per_child, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03100) + "ap_queue_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + if (ap_max_mem_free != APR_ALLOCATOR_MAX_FREE_UNLIMITED) { + /* If we want to conserve memory, let's not keep an unlimited number of + * pools & allocators. + * XXX: This should probably be a separate config directive + */ + max_recycled_pools = threads_per_child * 3 / 4 ; + } + rv = ap_queue_info_create(&worker_queue_info, pruntime, + threads_per_child, max_recycled_pools); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03101) + "ap_queue_info_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create the timeout mutex and main pollset before the listener + * thread starts. + */ + rv = apr_thread_mutex_create(&timeout_mutex, APR_THREAD_MUTEX_DEFAULT, + pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03102) + "creation of the timeout mutex failed."); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create the main pollset */ + pollset_flags = APR_POLLSET_THREADSAFE | APR_POLLSET_NOCOPY | + APR_POLLSET_NODEFAULT | APR_POLLSET_WAKEABLE; + for (i = 0; i < sizeof(good_methods) / sizeof(good_methods[0]); i++) { + rv = apr_pollset_create_ex(&event_pollset, pollset_size, pruntime, + pollset_flags, good_methods[i]); + if (rv == APR_SUCCESS) { + listener_is_wakeable = 1; + break; + } + } + if (rv != APR_SUCCESS) { + pollset_flags &= ~APR_POLLSET_WAKEABLE; + for (i = 0; i < sizeof(good_methods) / sizeof(good_methods[0]); i++) { + rv = apr_pollset_create_ex(&event_pollset, pollset_size, pruntime, + pollset_flags, good_methods[i]); + if (rv == APR_SUCCESS) { + break; + } + } + } + if (rv != APR_SUCCESS) { + pollset_flags &= ~APR_POLLSET_NODEFAULT; + rv = apr_pollset_create(&event_pollset, pollset_size, pruntime, + pollset_flags); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03103) + "apr_pollset_create with Thread Safety failed."); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Add listeners to the main pollset */ + listener_pollfd = apr_pcalloc(pruntime, num_listensocks * + sizeof(apr_pollfd_t)); + for (i = 0, lr = my_bucket->listeners; lr; lr = lr->next, i++) { + apr_pollfd_t *pfd; + listener_poll_type *pt; + + AP_DEBUG_ASSERT(i < num_listensocks); + pfd = &listener_pollfd[i]; + + pfd->reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR; + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = lr->sd; + + pt = apr_pcalloc(pruntime, sizeof(*pt)); + pfd->client_data = pt; + pt->type = PT_ACCEPT; + pt->baton = lr; + + apr_socket_opt_set(pfd->desc.s, APR_SO_NONBLOCK, 1); + apr_pollset_add(event_pollset, pfd); + + lr->accept_func = ap_unixd_accept; + } + + worker_sockets = apr_pcalloc(pruntime, threads_per_child * + sizeof(apr_socket_t *)); +} + +/* XXX under some circumstances not understood, children can get stuck + * in start_threads forever trying to take over slots which will + * never be cleaned up; for now there is an APLOG_DEBUG message issued + * every so often when this condition occurs + */ +static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy) +{ + thread_starter *ts = dummy; + apr_thread_t **threads = ts->threads; + apr_threadattr_t *thread_attr = ts->threadattr; + int my_child_num = ts->child_num_arg; + proc_info *my_info; + apr_status_t rv; + int threads_created = 0; + int listener_started = 0; + int prev_threads_created; + int loops, i; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02471) + "start_threads: Using %s (%swakeable)", + apr_pollset_method_name(event_pollset), + listener_is_wakeable ? "" : "not "); + + loops = prev_threads_created = 0; + while (1) { + /* threads_per_child does not include the listener thread */ + for (i = 0; i < threads_per_child; i++) { + int status = + ap_scoreboard_image->servers[my_child_num][i].status; + + if (status != SERVER_DEAD) { + continue; + } + + my_info = (proc_info *) ap_malloc(sizeof(proc_info)); + my_info->pslot = my_child_num; + my_info->tslot = i; + + /* We are creating threads right now */ + ap_update_child_status_from_indexes(my_child_num, i, + SERVER_STARTING, NULL); + /* We let each thread update its own scoreboard entry. This is + * done because it lets us deal with tid better. + */ + rv = ap_thread_create(&threads[i], thread_attr, + worker_thread, my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, + APLOGNO(03104) + "ap_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + threads_created++; + } + + /* Start the listener only when there are workers available */ + if (!listener_started && threads_created) { + create_listener_thread(ts); + listener_started = 1; + } + + + if (start_thread_may_exit || threads_created == threads_per_child) { + break; + } + /* wait for previous generation to clean up an entry */ + apr_sleep(apr_time_from_sec(1)); + ++loops; + if (loops % 120 == 0) { /* every couple of minutes */ + if (prev_threads_created == threads_created) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "child %" APR_PID_T_FMT " isn't taking over " + "slots very quickly (%d of %d)", + ap_my_pid, threads_created, + threads_per_child); + } + prev_threads_created = threads_created; + } + } + + /* What state should this child_main process be listed as in the + * scoreboard...? + * ap_update_child_status_from_indexes(my_child_num, i, SERVER_STARTING, + * (request_rec *) NULL); + * + * This state should be listed separately in the scoreboard, in some kind + * of process_status, not mixed in with the worker threads' status. + * "life_status" is almost right, but it's in the worker's structure, and + * the name could be clearer. gla + */ + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static void join_workers(apr_thread_t * listener, apr_thread_t ** threads) +{ + int i; + apr_status_t rv, thread_rv; + + if (listener) { + int iter; + + /* deal with a rare timing window which affects waking up the + * listener thread... if the signal sent to the listener thread + * is delivered between the time it verifies that the + * listener_may_exit flag is clear and the time it enters a + * blocking syscall, the signal didn't do any good... work around + * that by sleeping briefly and sending it again + */ + + iter = 0; + while (!dying) { + apr_sleep(apr_time_from_msec(500)); + if (dying || ++iter > 10) { + break; + } + /* listener has not stopped accepting yet */ + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "listener has not stopped accepting yet (%d iter)", iter); + wakeup_listener(); + } + if (iter > 10) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00475) + "the listener thread didn't stop accepting"); + } + else { + rv = apr_thread_join(&thread_rv, listener); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00476) + "apr_thread_join: unable to join listener thread"); + } + } + } + + for (i = 0; i < threads_per_child; i++) { + if (threads[i]) { /* if we ever created this thread */ + rv = apr_thread_join(&thread_rv, threads[i]); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00477) + "apr_thread_join: unable to join worker " + "thread %d", i); + } + } + } +} + +static void join_start_thread(apr_thread_t * start_thread_id) +{ + apr_status_t rv, thread_rv; + + start_thread_may_exit = 1; /* tell it to give up in case it is still + * trying to take over slots from a + * previous generation + */ + rv = apr_thread_join(&thread_rv, start_thread_id); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00478) + "apr_thread_join: unable to join the start " "thread"); + } +} + +static void child_main(int child_num_arg, int child_bucket) +{ + apr_thread_t **threads; + apr_status_t rv; + thread_starter *ts; + apr_threadattr_t *thread_attr; + apr_thread_t *start_thread_id; + int i; + + /* for benefit of any hooks that run as this child initializes */ + retained->mpm->mpm_state = AP_MPMQ_STARTING; + + ap_my_pid = getpid(); + ap_child_slot = child_num_arg; + ap_fatal_signal_child_setup(ap_server_conf); + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + +#if AP_HAS_THREAD_LOCAL + if (!one_process) { + apr_thread_t *thd = NULL; + if ((rv = ap_thread_main_create(&thd, pchild))) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10377) + "Couldn't initialize child main thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +#endif + + /* close unused listeners and pods */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (i != child_bucket) { + ap_close_listeners_ex(all_buckets[i].listeners); + ap_mpm_podx_close(all_buckets[i].pod); + } + } + + /*stuff to do before we switch id's, so we have permissions. */ + ap_reopen_scoreboard(pchild, NULL, 0); + + /* done with init critical section */ + if (ap_run_drop_privileges(pchild, ap_server_conf)) { + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Just use the standard apr_setup_signal_thread to block all signals + * from being received. The child processes no longer use signals for + * any communication with the parent process. Let's also do this before + * child_init() hooks are called and possibly create threads that + * otherwise could "steal" (implicitly) MPM's signals. + */ + rv = apr_setup_signal_thread(); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00479) + "Couldn't initialize signal thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_run_child_init(pchild, ap_server_conf); + + if (ap_max_requests_per_child) { + conns_this_child = ap_max_requests_per_child; + } + else { + /* coding a value of zero means infinity */ + conns_this_child = APR_INT32_MAX; + } + + /* Setup threads */ + + /* Globals used by signal_threads() so to be initialized before */ + setup_threads_runtime(); + + /* clear the storage; we may not create all our threads immediately, + * and we want a 0 entry to indicate a thread which was not created + */ + threads = ap_calloc(threads_per_child, sizeof(apr_thread_t *)); + ts = apr_palloc(pchild, sizeof(*ts)); + + apr_threadattr_create(&thread_attr, pchild); + /* 0 means PTHREAD_CREATE_JOINABLE */ + apr_threadattr_detach_set(thread_attr, 0); + + if (ap_thread_stacksize != 0) { + rv = apr_threadattr_stacksize_set(thread_attr, ap_thread_stacksize); + if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(02436) + "WARNING: ThreadStackSize of %" APR_SIZE_T_FMT " is " + "inappropriate, using default", + ap_thread_stacksize); + } + } + + ts->threads = threads; + ts->listener = NULL; + ts->child_num_arg = child_num_arg; + ts->threadattr = thread_attr; + + rv = ap_thread_create(&start_thread_id, thread_attr, start_threads, + ts, pchild); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00480) + "ap_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + /* If we are only running in one_process mode, we will want to + * still handle signals. */ + if (one_process) { + /* Block until we get a terminating signal. */ + apr_signal_thread(check_signal); + /* make sure the start thread has finished; signal_threads() + * and join_workers() depend on that + */ + /* XXX join_start_thread() won't be awakened if one of our + * threads encounters a critical error and attempts to + * shutdown this child + */ + join_start_thread(start_thread_id); + + /* helps us terminate a little more quickly than the dispatch of the + * signal thread; beats the Pipe of Death and the browsers + */ + signal_threads(ST_UNGRACEFUL); + + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + join_workers(ts->listener, threads); + } + 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 MaxConnectionsPerChild) it will send us SIGTERM + */ + apr_signal(SIGTERM, dummy_signal_handler); + unblock_signal(SIGTERM); + /* Watch for any messages from the parent over the POD */ + while (1) { + rv = ap_mpm_podx_check(my_bucket->pod); + if (rv == AP_MPM_PODX_NORESTART) { + /* see if termination was triggered while we slept */ + switch (terminate_mode) { + case ST_GRACEFUL: + rv = AP_MPM_PODX_GRACEFUL; + break; + case ST_UNGRACEFUL: + rv = AP_MPM_PODX_RESTART; + break; + } + } + if (rv == AP_MPM_PODX_GRACEFUL || rv == AP_MPM_PODX_RESTART) { + /* make sure the start thread has finished; + * signal_threads() and join_workers depend on that + */ + join_start_thread(start_thread_id); + signal_threads(rv == + AP_MPM_PODX_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL); + break; + } + } + + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "%s termination received, joining workers", + rv == AP_MPM_PODX_GRACEFUL ? "graceful" : "ungraceful"); + join_workers(ts->listener, threads); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "%s termination, workers joined, exiting", + rv == AP_MPM_PODX_GRACEFUL ? "graceful" : "ungraceful"); + } + + free(threads); + + clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0); +} + +static int make_child(server_rec * s, int slot, int bucket) +{ + int pid; + + if (slot + 1 > retained->max_daemon_used) { + retained->max_daemon_used = slot + 1; + } + + if (ap_scoreboard_image->parent[slot].pid != 0) { + /* XXX replace with assert or remove ? */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(03455) + "BUG: Scoreboard slot %d should be empty but is " + "in use by pid %" APR_PID_T_FMT, + slot, ap_scoreboard_image->parent[slot].pid); + return -1; + } + + if (one_process) { + my_bucket = &all_buckets[0]; + + event_note_child_started(slot, getpid()); + child_main(slot, 0); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + if ((pid = fork()) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00481) + "fork: Unable to fork new process"); + + /* fork didn't succeed. There's no need to touch the scoreboard; + * if we were trying to replace a failed child process, then + * server_main_loop() marked its workers SERVER_DEAD, and if + * we were trying to replace a child process that exited normally, + * its worker_thread()s left SERVER_DEAD or SERVER_GRACEFUL behind. + */ + + /* In case system resources are maxxed out, we don't want + Apache running away with the CPU trying to fork over and + over and over again. */ + apr_sleep(apr_time_from_sec(10)); + + return -1; + } + + if (!pid) { +#if AP_HAS_THREAD_LOCAL + ap_thread_current_after_fork(); +#endif + + my_bucket = &all_buckets[bucket]; + +#ifdef HAVE_BINDPROCESSOR + /* By default, AIX binds to a single processor. This bit unbinds + * children which will then bind to another CPU. + */ + int status = bindprocessor(BINDPROCESS, (int) getpid(), + PROCESSOR_CLASS_ANY); + if (status != OK) + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00482) + "processor unbind failed"); +#endif + RAISE_SIGSTOP(MAKE_CHILD); + + apr_signal(SIGTERM, just_die); + child_main(slot, bucket); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + event_note_child_started(slot, pid); + return 0; +} + +/* start up a bunch of children */ +static void startup_children(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < server_limit; ++i) { + if (ap_scoreboard_image->parent[i].pid != 0) { + continue; + } + if (make_child(ap_server_conf, i, i % retained->mpm->num_buckets) < 0) { + break; + } + --number_to_start; + } +} + +static void perform_idle_server_maintenance(int child_bucket, + int *max_daemon_used) +{ + int num_buckets = retained->mpm->num_buckets; + int idle_thread_count = 0; + process_score *ps; + int free_length = 0; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead = -1; + int active_thread_count = 0; + int i, j; + + for (i = 0; i < server_limit; ++i) { + if (num_buckets > 1 && (i % num_buckets) != child_bucket) { + /* We only care about child_bucket in this call */ + continue; + } + if (i >= retained->max_daemon_used && + free_length == retained->idle_spawn_rate[child_bucket]) { + /* short cut if all active processes have been examined and + * enough empty scoreboard slots have been found + */ + break; + } + + ps = &ap_scoreboard_image->parent[i]; + if (ps->pid != 0) { + int child_threads_active = 0; + if (ps->quiescing == 1) { + ps->quiescing = 2; + retained->active_daemons--; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d quiescing: pid %d, gen %d, " + "active %d/%d, total %d/%d/%d", + i, (int)ps->pid, (int)ps->generation, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit); + } + for (j = 0; j < threads_per_child; j++) { + int status = ap_scoreboard_image->servers[i][j].status; + + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (status <= SERVER_READY && !ps->quiescing && !ps->not_accepting + && ps->generation == retained->mpm->my_generation) { + ++idle_thread_count; + } + if (status >= SERVER_READY && status < SERVER_GRACEFUL) { + ++child_threads_active; + } + } + active_thread_count += child_threads_active; + if (child_threads_active == threads_per_child) { + had_healthy_child = 1; + } + last_non_dead = i; + } + else if (free_length < retained->idle_spawn_rate[child_bucket]) { + free_slots[free_length++] = i; + } + } + if (*max_daemon_used < last_non_dead + 1) { + *max_daemon_used = last_non_dead + 1; + } + + if (retained->sick_child_detected) { + if (had_healthy_child) { + /* Assume this is a transient error, even though it may not be. Leave + * the server up in case it is able to serve some requests or the + * problem will be resolved. + */ + retained->sick_child_detected = 0; + } + else if (child_bucket < num_buckets - 1) { + /* check for had_healthy_child up to the last child bucket */ + return; + } + else { + /* looks like a basket case, as no child ever fully initialized; give up. + */ + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, + ap_server_conf, APLOGNO(02324) + "A resource shortage or other unrecoverable failure " + "was encountered before any child process initialized " + "successfully... httpd is exiting!"); + /* the child already logged the failure details */ + return; + } + } + + AP_DEBUG_ASSERT(retained->active_daemons <= retained->total_daemons + && retained->total_daemons <= retained->max_daemon_used + && retained->max_daemon_used <= server_limit); + + if (idle_thread_count > max_spare_threads / num_buckets) { + /* + * Child processes that we ask to shut down won't die immediately + * but may stay around for a long time when they finish their + * requests. If the server load changes many times, many such + * gracefully finishing processes may accumulate, filling up the + * scoreboard. To avoid running out of scoreboard entries, we + * don't shut down more processes if there are stopping ones + * already (i.e. active_daemons != total_daemons) and not enough + * slack space in the scoreboard for a graceful restart. + * + * XXX It would be nice if we could + * XXX - kill processes without keepalive connections first + * XXX - tell children to stop accepting new connections, and + * XXX depending on server load, later be able to resurrect them + * or kill them + */ + int do_kill = (retained->active_daemons == retained->total_daemons + || (server_limit - retained->total_daemons > + active_daemons_limit)); + ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, ap_server_conf, + "%shutting down one child: " + "active %d/%d, total %d/%d/%d, " + "idle threads %d, max workers %d", + (do_kill) ? "S" : "Not s", + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit, idle_thread_count, max_workers); + if (do_kill) { + ap_mpm_podx_signal(all_buckets[child_bucket].pod, + AP_MPM_PODX_GRACEFUL); + } + else { + /* Wait for dying daemon(s) to exit */ + } + retained->idle_spawn_rate[child_bucket] = 1; + } + else if (idle_thread_count < min_spare_threads / num_buckets) { + if (active_thread_count >= max_workers / num_buckets) { + if (0 == idle_thread_count) { + if (!retained->maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00484) + "server reached MaxRequestWorkers setting, " + "consider raising the MaxRequestWorkers " + "setting"); + retained->maxclients_reported = 1; + } + } + else { + if (!retained->near_maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(10159) + "server is within MinSpareThreads of " + "MaxRequestWorkers, consider raising the " + "MaxRequestWorkers setting"); + retained->near_maxclients_reported = 1; + } + } + retained->idle_spawn_rate[child_bucket] = 1; + } + else if (free_length == 0) { /* scoreboard is full, can't fork */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(03490) + "scoreboard is full, not at MaxRequestWorkers." + "Increase ServerLimit."); + retained->idle_spawn_rate[child_bucket] = 1; + } + else { + if (free_length > retained->idle_spawn_rate[child_bucket]) { + free_length = retained->idle_spawn_rate[child_bucket]; + } + if (free_length + retained->active_daemons > active_daemons_limit) { + if (retained->active_daemons < active_daemons_limit) { + free_length = active_daemons_limit - retained->active_daemons; + } + else { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "server is at active daemons limit, spawning " + "of %d children cancelled: active %d/%d, " + "total %d/%d/%d, rate %d", free_length, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit, retained->idle_spawn_rate[child_bucket]); + /* reset the spawning rate and prevent its growth below */ + retained->idle_spawn_rate[child_bucket] = 1; + ++retained->hold_off_on_exponential_spawning; + free_length = 0; + } + } + if (retained->idle_spawn_rate[child_bucket] >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00486) + "server seems busy, (you may need " + "to increase StartServers, ThreadsPerChild " + "or Min/MaxSpareThreads), " + "spawning %d children, there are around %d idle " + "threads, %d active children, and %d children " + "that are shutting down", free_length, + idle_thread_count, retained->active_daemons, + retained->total_daemons); + } + for (i = 0; i < free_length; ++i) { + int slot = free_slots[i]; + if (make_child(ap_server_conf, slot, child_bucket) < 0) { + continue; + } + if (*max_daemon_used < slot + 1) { + *max_daemon_used = slot + 1; + } + } + /* 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 (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; + } + else if (retained->idle_spawn_rate[child_bucket] + < MAX_SPAWN_RATE / num_buckets) { + retained->idle_spawn_rate[child_bucket] *= 2; + } + } + } + else { + retained->idle_spawn_rate[child_bucket] = 1; + } +} + +static void server_main_loop(int remaining_children_to_start) +{ + int num_buckets = retained->mpm->num_buckets; + int max_daemon_used = 0; + int successive_kills = 0; + int child_slot; + apr_exit_why_e exitwhy; + int status, processed_status; + apr_proc_t pid; + int i; + + while (!retained->mpm->restart_pending && !retained->mpm->shutdown_pending) { + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); + + if (pid.pid != -1) { + processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); + if (processed_status == APEXIT_CHILDFATAL) { + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->mpm->my_generation) { + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + /* + * total_daemons counting will be off now, but as we + * are shutting down, that is not an issue anymore. + */ + return; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00487) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + retained->sick_child_detected = 1; + } + } + else if (processed_status == APEXIT_CHILDSICK) { + /* tell perform_idle_server_maintenance to check into this + * on the next timer pop + */ + retained->sick_child_detected = 1; + } + /* non-fatal death... note that it's gone in the scoreboard. */ + if (child_slot >= 0) { + event_note_child_stopped(child_slot, 0, 0); + + if (processed_status == APEXIT_CHILDSICK) { + /* resource shortage, minimize the fork rate */ + retained->idle_spawn_rate[child_slot % num_buckets] = 1; + } + else if (remaining_children_to_start) { + /* we're still doing a 1-for-1 replacement of dead + * children with new children + */ + make_child(ap_server_conf, child_slot, + child_slot % num_buckets); + --remaining_children_to_start; + } + } +#if APR_HAS_OTHER_CHILD + else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, + status) == 0) { + /* handled */ + } +#endif + else if (retained->mpm->was_graceful) { + /* Great, we've probably just lost a slot in the + * scoreboard. Somehow we don't know about this child. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(00488) + "long lost child came home! (pid %ld)", + (long) pid.pid); + } + /* Don't perform idle maintenance when a child dies, + * only do it when there's a timeout. Remember only a + * finite number of children can die, and it's pretty + * pathological for a lot to die suddenly. If a child is + * killed by a signal (faulting) we want to restart it ASAP + * though, up to 3 successive faults or we stop this until + * a timeout happens again (to avoid the flood of fork()ed + * processes that keep being killed early). + */ + if (child_slot < 0 || !APR_PROC_CHECK_SIGNALED(exitwhy)) { + continue; + } + if (++successive_kills >= 3) { + if (successive_kills % 10 == 3) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(10392) + "children are killed successively!"); + } + continue; + } + ++remaining_children_to_start; + } + else { + successive_kills = 0; + } + + if (remaining_children_to_start) { + /* we hit a 1 second timeout in which none of the previous + * generation of children needed to be reaped... so assume + * they're all done, and pick up the slack if any is left. + */ + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + /* In any event we really shouldn't do the code below because + * few of the servers we just started are in the IDLE state + * yet, so we'd mistakenly create an extra server. + */ + continue; + } + + max_daemon_used = 0; + for (i = 0; i < num_buckets; i++) { + perform_idle_server_maintenance(i, &max_daemon_used); + } + retained->max_daemon_used = max_daemon_used; + } +} + +static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) +{ + int num_buckets = retained->mpm->num_buckets; + int remaining_children_to_start; + int i; + + ap_log_pid(pconf, ap_pid_fname); + + if (!retained->mpm->was_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + /* fix the generation number in the global score; we just got a new, + * cleared scoreboard + */ + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + } + + ap_unixd_mpm_set_signals(pconf, one_process); + + /* Don't thrash since num_buckets depends on the + * system and the number of online CPU cores... + */ + if (active_daemons_limit < num_buckets) + active_daemons_limit = num_buckets; + if (ap_daemons_to_start < num_buckets) + ap_daemons_to_start = num_buckets; + /* We want to create as much children at a time as the number of buckets, + * so to optimally accept connections (evenly distributed across buckets). + * Thus min_spare_threads should at least maintain num_buckets children, + * and max_spare_threads allow num_buckets more children w/o triggering + * immediately (e.g. num_buckets idle threads margin, one per bucket). + */ + if (min_spare_threads < threads_per_child * (num_buckets - 1) + num_buckets) + min_spare_threads = threads_per_child * (num_buckets - 1) + num_buckets; + if (max_spare_threads < min_spare_threads + (threads_per_child + 1) * num_buckets) + max_spare_threads = min_spare_threads + (threads_per_child + 1) * num_buckets; + + /* If we're doing a graceful_restart then we're going to see a lot + * of children exiting immediately when we get into the main loop + * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty + * rapidly... and for each one that exits we may start a new one, until + * there are at least min_spare_threads idle threads, counting across + * all children. But we may be permitted to start more children than + * that, so we'll just keep track of how many we're + * supposed to start up without the 1 second penalty between each fork. + */ + remaining_children_to_start = ap_daemons_to_start; + if (remaining_children_to_start > active_daemons_limit) { + remaining_children_to_start = active_daemons_limit; + } + if (!retained->mpm->was_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 */ + retained->hold_off_on_exponential_spawning = 10; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00489) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00490) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + server_main_loop(remaining_children_to_start); + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { + /* Time to shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + event_note_child_stopped); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, + ap_server_conf, APLOGNO(00491) "caught SIGTERM, shutting down"); + } + + return DONE; + } + + if (retained->mpm->shutdown_pending) { + /* Time to gracefully shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + int active_children; + int index; + apr_time_t cutoff = 0; + + /* Close our listeners, and then ask our children to do same */ + ap_close_listeners(); + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + ap_relieve_child_processes(event_note_child_stopped); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00492) + "caught " AP_SIG_GRACEFUL_STOP_STRING + ", shutting down gracefully"); + } + + if (ap_graceful_shutdown_timeout) { + cutoff = apr_time_now() + + apr_time_from_sec(ap_graceful_shutdown_timeout); + } + + /* Don't really exit until each child has finished */ + retained->mpm->shutdown_pending = 0; + do { + /* Pause for a second */ + apr_sleep(apr_time_from_sec(1)); + + /* Relieve any children which have now exited */ + ap_relieve_child_processes(event_note_child_stopped); + + active_children = 0; + for (index = 0; index < retained->max_daemon_used; ++index) { + if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) { + active_children = 1; + /* Having just one child is enough to stay around */ + break; + } + } + } while (!retained->mpm->shutdown_pending && active_children && + (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff)); + + /* We might be here because we received SIGTERM, either + * way, try and make sure that all of our processes are + * really dead. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, event_note_child_stopped); + + return DONE; + } + + /* we've been told to restart */ + if (one_process) { + /* not worth thinking about */ + return DONE; + } + + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++retained->mpm->my_generation; + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + + if (!retained->mpm->is_ungraceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00493) + AP_SIG_GRACEFUL_STRING " received. Doing graceful restart"); + /* wake up the children...time to die. But we'll have more soon */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + + /* This is mostly for debugging... so that we know what is still + * gracefully dealing with existing request. + */ + + } + else { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00494) + "SIGHUP received. Attempting to restart"); + /* Kill 'em all. Since the child acts the same on the parents SIGTERM + * and a SIGHUP, we may as well use the same signal, because some user + * pthreads are stealing signals from us left and right. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, + AP_MPM_PODX_RESTART); + } + + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + event_note_child_stopped); + } + + return OK; +} + +static void setup_slave_conn(conn_rec *c, void *csd) +{ + event_conn_state_t *mcs; + event_conn_state_t *cs; + + mcs = ap_get_module_config(c->master->conn_config, &mpm_event_module); + + cs = apr_pcalloc(c->pool, sizeof(*cs)); + cs->c = c; + cs->r = NULL; + cs->sc = mcs->sc; + cs->suspended = 0; + cs->p = c->pool; + cs->bucket_alloc = c->bucket_alloc; + cs->pfd = mcs->pfd; + cs->pub = mcs->pub; + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + cs->pub.sense = CONN_SENSE_DEFAULT; + + c->cs = &(cs->pub); + ap_set_module_config(c->conn_config, &mpm_event_module, cs); +} + +static int event_pre_connection(conn_rec *c, void *csd) +{ + if (c->master && (!c->cs || c->cs == c->master->cs)) { + setup_slave_conn(c, csd); + } + return OK; +} + +static int event_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, + const char *protocol) +{ + if (!r && s) { + /* connection based switching of protocol, set the correct server + * configuration, so that timeouts, keepalives and such are used + * for the server that the connection was switched on. + * Normally, we set this on post_read_request, but on a protocol + * other than http/1.1, this might never happen. + */ + event_conn_state_t *cs; + + cs = ap_get_module_config(c->conn_config, &mpm_event_module); + cs->sc = ap_get_module_config(s->module_config, &mpm_event_module); + } + return DECLINED; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int event_open_logs(apr_pool_t * p, apr_pool_t * plog, + apr_pool_t * ptemp, server_rec * s) +{ + int startup = 0; + int level_flags = 0; + int num_buckets = 0; + ap_listen_rec **listen_buckets; + apr_status_t rv; + int i; + + pconf = p; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } + + if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "no listening sockets available, shutting down"); + return !OK; + } + + if (one_process) { + num_buckets = 1; + } + else if (retained->mpm->was_graceful) { + /* Preserve the number of buckets on graceful restarts. */ + num_buckets = retained->mpm->num_buckets; + } + if ((rv = ap_duplicate_listeners(pconf, ap_server_conf, + &listen_buckets, &num_buckets))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not duplicate listeners"); + return !OK; + } + + all_buckets = apr_pcalloc(pconf, num_buckets * sizeof(*all_buckets)); + for (i = 0; i < num_buckets; i++) { + if (!one_process && /* no POD in one_process mode */ + (rv = ap_mpm_podx_open(pconf, &all_buckets[i].pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); + return !OK; + } + all_buckets[i].listeners = listen_buckets[i]; + } + + if (retained->mpm->max_buckets < num_buckets) { + int new_max, *new_ptr; + new_max = retained->mpm->max_buckets * 2; + if (new_max < num_buckets) { + new_max = num_buckets; + } + new_ptr = (int *)apr_palloc(ap_pglobal, new_max * sizeof(int)); + if (retained->idle_spawn_rate) /* NULL at startup */ + memcpy(new_ptr, retained->idle_spawn_rate, + retained->mpm->num_buckets * sizeof(int)); + retained->idle_spawn_rate = new_ptr; + retained->mpm->max_buckets = new_max; + } + if (retained->mpm->num_buckets < num_buckets) { + int rate_max = 1; + /* If new buckets are added, set their idle spawn rate to + * the highest so far, so that they get filled as quickly + * as the existing ones. + */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (rate_max < retained->idle_spawn_rate[i]) { + rate_max = retained->idle_spawn_rate[i]; + } + } + for (/* up to date i */; i < num_buckets; i++) { + retained->idle_spawn_rate[i] = rate_max; + } + } + retained->mpm->num_buckets = num_buckets; + + /* for skiplist */ + srand((unsigned int)apr_time_now()); + return OK; +} + +static int event_pre_config(apr_pool_t * pconf, apr_pool_t * plog, + apr_pool_t * ptemp) +{ + int no_detach, debug, foreground; + apr_status_t rv; + const char *userdata_key = "mpm_event_module"; + int test_atomics = 0; + + debug = ap_exists_config_define("DEBUG"); + + if (debug) { + foreground = one_process = 1; + no_detach = 0; + } + else { + one_process = ap_exists_config_define("ONE_PROCESS"); + no_detach = ap_exists_config_define("NO_DETACH"); + foreground = ap_exists_config_define("FOREGROUND"); + } + + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->mpm = ap_unixd_mpm_get_retained_data(); + if (retained->mpm->module_loads) { + test_atomics = 1; + } + } + retained->mpm->mpm_state = AP_MPMQ_STARTING; + if (retained->mpm->baton != retained) { + retained->mpm->was_graceful = 0; + retained->mpm->baton = retained; + } + ++retained->mpm->module_loads; + + /* test once for correct operation of fdqueue */ + if (test_atomics || retained->mpm->module_loads == 2) { + static apr_uint32_t foo1, foo2; + + apr_atomic_set32(&foo1, 100); + foo2 = apr_atomic_add32(&foo1, -10); + if (foo2 != 100 || foo1 != 90) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(02405) + "atomics not working as expected - add32 of negative number"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + /* sigh, want this only the second time around */ + if (retained->mpm->module_loads == 2) { + rv = apr_pollset_create(&event_pollset, 1, plog, + APR_POLLSET_THREADSAFE | APR_POLLSET_NOCOPY); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00495) + "Couldn't create a Thread Safe Pollset. " + "Is it supported on your platform?" + "Also check system or user limits!"); + return HTTP_INTERNAL_SERVER_ERROR; + } + apr_pollset_destroy(event_pollset); + + if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); + rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND + : APR_PROC_DETACH_DAEMONIZE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00496) + "apr_proc_detach failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + 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; + max_spare_threads = DEFAULT_MAX_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD; + server_limit = DEFAULT_SERVER_LIMIT; + thread_limit = DEFAULT_THREAD_LIMIT; + active_daemons_limit = server_limit; + threads_per_child = DEFAULT_THREADS_PER_CHILD; + max_workers = active_daemons_limit * threads_per_child; + defer_linger_chain = NULL; + had_healthy_child = 0; + ap_extended_status = 0; + + event_pollset = NULL; + worker_queue_info = NULL; + listener_os_thread = NULL; + listensocks_disabled = 0; + listener_is_wakeable = 0; + + return OK; +} + +static int event_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + struct { + struct timeout_queue *tail, *q; + apr_hash_t *hash; + } wc, ka; + + /* Not needed in pre_config stage */ + if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { + return OK; + } + + wc.tail = ka.tail = NULL; + wc.hash = apr_hash_make(ptemp); + ka.hash = apr_hash_make(ptemp); + + linger_q = TO_QUEUE_MAKE(pconf, apr_time_from_sec(MAX_SECS_TO_LINGER), + NULL); + short_linger_q = TO_QUEUE_MAKE(pconf, apr_time_from_sec(SECONDS_TO_LINGER), + NULL); + + for (; s; s = s->next) { + event_srv_cfg *sc = apr_pcalloc(pconf, sizeof *sc); + + ap_set_module_config(s->module_config, &mpm_event_module, sc); + if (!wc.tail) { + /* The main server uses the global queues */ + wc.q = TO_QUEUE_MAKE(pconf, s->timeout, NULL); + apr_hash_set(wc.hash, &s->timeout, sizeof s->timeout, wc.q); + wc.tail = write_completion_q = wc.q; + + ka.q = TO_QUEUE_MAKE(pconf, s->keep_alive_timeout, NULL); + apr_hash_set(ka.hash, &s->keep_alive_timeout, + sizeof s->keep_alive_timeout, ka.q); + ka.tail = keepalive_q = ka.q; + } + else { + /* The vhosts use any existing queue with the same timeout, + * or their own queue(s) if there isn't */ + wc.q = apr_hash_get(wc.hash, &s->timeout, sizeof s->timeout); + if (!wc.q) { + wc.q = TO_QUEUE_MAKE(pconf, s->timeout, wc.tail); + apr_hash_set(wc.hash, &s->timeout, sizeof s->timeout, wc.q); + wc.tail = wc.tail->next = wc.q; + } + + ka.q = apr_hash_get(ka.hash, &s->keep_alive_timeout, + sizeof s->keep_alive_timeout); + if (!ka.q) { + ka.q = TO_QUEUE_MAKE(pconf, s->keep_alive_timeout, ka.tail); + apr_hash_set(ka.hash, &s->keep_alive_timeout, + sizeof s->keep_alive_timeout, ka.q); + ka.tail = ka.tail->next = ka.q; + } + } + sc->wc_q = wc.q; + sc->ka_q = ka.q; + } + + return OK; +} + +static int event_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00497) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of %d servers, decreasing to %d.", + server_limit, MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00498) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00499) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00500) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00501) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00502) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of %d threads, decreasing to %d.", + thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00503) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00504) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00505) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* you cannot change ThreadLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_thread_limit) { + retained->first_thread_limit = thread_limit; + } + else if (thread_limit != retained->first_thread_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00506) + "changing ThreadLimit to %d from original value of %d " + "not allowed during restart", + thread_limit, retained->first_thread_limit); + thread_limit = retained->first_thread_limit; + } + + if (threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00507) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of %d threads, decreasing to %d. " + "To increase, please see the ThreadLimit directive.", + threads_per_child, thread_limit, thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00508) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + threads_per_child, thread_limit); + } + threads_per_child = thread_limit; + } + else if (threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00509) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00510) + "ThreadsPerChild of %d not allowed, increasing to 1", + threads_per_child); + } + threads_per_child = 1; + } + + if (max_workers < threads_per_child) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00511) + "WARNING: MaxRequestWorkers of %d is less than " + "ThreadsPerChild of %d, increasing to %d. " + "MaxRequestWorkers must be at least as large " + "as the number of threads in a single server.", + max_workers, threads_per_child, threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00512) + "MaxRequestWorkers of %d is less than ThreadsPerChild " + "of %d, increasing to match", + max_workers, threads_per_child); + } + max_workers = threads_per_child; + } + + active_daemons_limit = max_workers / threads_per_child; + + if (max_workers % threads_per_child) { + int tmp_max_workers = active_daemons_limit * threads_per_child; + + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00513) + "WARNING: MaxRequestWorkers of %d is not an integer " + "multiple of ThreadsPerChild of %d, decreasing to nearest " + "multiple %d, for a maximum of %d servers.", + max_workers, threads_per_child, tmp_max_workers, + active_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00514) + "MaxRequestWorkers of %d is not an integer multiple " + "of ThreadsPerChild of %d, decreasing to nearest " + "multiple %d", max_workers, threads_per_child, + tmp_max_workers); + } + max_workers = tmp_max_workers; + } + + if (active_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00515) + "WARNING: MaxRequestWorkers of %d would require %d servers " + "and would exceed ServerLimit of %d, decreasing to %d. " + "To increase, please see the ServerLimit directive.", + max_workers, active_daemons_limit, server_limit, + server_limit * threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00516) + "MaxRequestWorkers of %d would require %d servers and " + "exceed ServerLimit of %d, decreasing to %d", + max_workers, active_daemons_limit, server_limit, + server_limit * threads_per_child); + } + active_daemons_limit = server_limit; + } + + /* ap_daemons_to_start > active_daemons_limit checked in ap_mpm_run() */ + if (ap_daemons_to_start < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00517) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00518) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00519) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server " + "failure. Please read the documentation.", + min_spare_threads); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00520) + "MinSpareThreads of %d not allowed, increasing to 1", + min_spare_threads); + } + min_spare_threads = 1; + } + + /* max_spare_threads < min_spare_threads + threads_per_child + * checked in ap_mpm_run() + */ + + return OK; +} + +static void event_hooks(apr_pool_t * p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = { "core.c", NULL }; + one_process = 0; + + ap_hook_open_logs(event_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + /* we need to set the MPM state before other pre-config hooks use MPM query + * to retrieve it, so register as REALLY_FIRST + */ + ap_hook_pre_config(event_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_post_config(event_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(event_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(event_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(event_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_register_timed_callback(event_register_timed_callback, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_pre_read_request(event_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_read_request(event_post_read_request, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(event_get_name, NULL, NULL, APR_HOOK_MIDDLE); + + ap_hook_pre_connection(event_pre_connection, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_protocol_switch(event_protocol_switch, NULL, NULL, APR_HOOK_REALLY_FIRST); +} + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_spare_threads(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + min_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_spare_threads(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + max_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_workers(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00521) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); + } + max_workers = atoi(arg); + return NULL; +} + +static const char *set_threads_per_child(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + threads_per_child = atoi(arg); + return NULL; +} +static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + server_limit = atoi(arg); + return NULL; +} + +static const char *set_thread_limit(cmd_parms * cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + thread_limit = atoi(arg); + return NULL; +} + +static const char *set_worker_factor(cmd_parms * cmd, void *dummy, + const char *arg) +{ + double val; + char *endptr; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + val = strtod(arg, &endptr); + if (*endptr) + return "error parsing value"; + + if (val <= 0) + return "AsyncRequestWorkerFactor argument must be a positive number"; + + worker_factor = val * WORKER_FACTOR_SCALE; + if (worker_factor < WORKER_FACTOR_SCALE) { + worker_factor = WORKER_FACTOR_SCALE; + } + return NULL; +} + + +static const command_rec event_cmds[] = { + LISTEN_COMMANDS, + AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup"), + AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF, + "Maximum number of child processes for this run of Apache"), + AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, + "Minimum number of idle threads, to handle request spikes"), + AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, + "Maximum number of idle threads"), + AP_INIT_TAKE1("MaxClients", set_max_workers, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), + AP_INIT_TAKE1("MaxRequestWorkers", set_max_workers, NULL, RSRC_CONF, + "Maximum number of threads alive at the same time"), + AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates"), + AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum number of worker threads per child process for this " + "run of Apache - Upper limit for ThreadsPerChild"), + AP_INIT_TAKE1("AsyncRequestWorkerFactor", set_worker_factor, NULL, RSRC_CONF, + "How many additional connects will be accepted per idle " + "worker thread"), + AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, + {NULL} +}; + +AP_DECLARE_MODULE(mpm_event) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + event_cmds, /* command apr_table_t */ + event_hooks /* register_hooks */ +}; diff --git a/server/mpm/event/mpm_default.h b/server/mpm/event/mpm_default.h new file mode 100644 index 0000000..214caa0 --- /dev/null +++ b/server/mpm/event/mpm_default.h @@ -0,0 +1,56 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** + * @file event/mpm_default.h + * @brief Event MPM defaults + * + * @defgroup APACHE_MPM_EVENT Event MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 3 +#endif + +/* Maximum number of *free* server processes --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_DAEMON +#define DEFAULT_MAX_FREE_DAEMON 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_DAEMON +#define DEFAULT_MIN_FREE_DAEMON 3 +#endif + +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 25 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/mpmt_os2/Makefile.in b/server/mpm/mpmt_os2/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/mpmt_os2/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/mpmt_os2/config.m4 b/server/mpm/mpmt_os2/config.m4 new file mode 100644 index 0000000..9a29903 --- /dev/null +++ b/server/mpm/mpmt_os2/config.m4 @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(if mpmt_os2 MPM supports this platform) +case $host in + *os2-emx*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(mpmt_os2, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/mpmt_os2/config5.m4 b/server/mpm/mpmt_os2/config5.m4 new file mode 100644 index 0000000..c74f145 --- /dev/null +++ b/server/mpm/mpmt_os2/config5.m4 @@ -0,0 +1,3 @@ +APACHE_MPM_MODULE(mpmt_os2, $enable_mpm_mpmt_os2, mpmt_os2.lo mpmt_os2_child.lo,[ + APR_ADDTO(CFLAGS,-Zmt) +]) diff --git a/server/mpm/mpmt_os2/mpm_default.h b/server/mpm/mpmt_os2/mpm_default.h new file mode 100644 index 0000000..36ba944 --- /dev/null +++ b/server/mpm/mpmt_os2/mpm_default.h @@ -0,0 +1,57 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file mpmt_os2/mpm_default.h + * @brief os2 MPM defaults + * + * @defgroup APACHE_MPM_OS2 OS/2 MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers processes to spawn off by default + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 2 +#endif + +/* Maximum number of *free* server threads --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_SPARE_THREAD +#define DEFAULT_MAX_SPARE_THREAD 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_SPARE_THREAD +#define DEFAULT_MIN_SPARE_THREAD 5 +#endif + +/* + * Interval, in microseconds, between scoreboard maintenance. + */ +#ifndef SCOREBOARD_MAINTENANCE_INTERVAL +#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/mpmt_os2/mpmt_os2.c b/server/mpm/mpmt_os2/mpmt_os2.c new file mode 100644 index 0000000..b3adb03 --- /dev/null +++ b/server/mpm/mpmt_os2/mpmt_os2.c @@ -0,0 +1,614 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Multi-process, multi-threaded MPM for OS/2 + * + * Server consists of + * - a main, parent process + * - a small, static number of child processes + * + * The parent process's job is to manage the child processes. This involves + * spawning children as required to ensure there are always ap_daemons_to_start + * processes accepting connections. + * + * Each child process consists of a pool of worker threads and a + * main thread that accepts connections & passes them to the workers via + * a work queue. The worker thread pool is dynamic, managed by a maintenance + * thread so that the number of idle threads is kept between + * min_spare_threads & max_spare_threads. + * + */ + +/* + Todo list + - Enforce MaxRequestWorkers somehow +*/ +#define INCL_NOPMAPI +#define INCL_DOS +#define INCL_DOSERRORS + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "apr_portable.h" +#include "mpm_common.h" +#include "scoreboard.h" +#include "apr_strings.h" +#include <os2.h> +#include <process.h> + +/* We don't need many processes, + * they're only for redundancy in the event of a crash + */ +#define HARD_SERVER_LIMIT 10 + +/* Limit on the total number of threads per process + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 256 +#endif + +server_rec *ap_server_conf; +static apr_pool_t *pconf = NULL; /* Pool for config stuff */ + +/* Config globals */ +static int one_process = 0; +static int ap_daemons_to_start = 0; +static int ap_thread_limit = 0; +int ap_min_spare_threads = 0; +int ap_max_spare_threads = 0; + +/* Keep track of a few interesting statistics */ +int ap_max_daemons_limit = 0; + +/* volatile just in case */ +static int volatile shutdown_pending; +static int volatile restart_pending; +static int volatile is_graceful = 0; +ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */ +static int is_parent_process=TRUE; +HMTX ap_mpm_accept_mutex = 0; + +/* An array of these is stored in a shared memory area for passing + * sockets from the parent to child processes + */ +typedef struct { + struct sockaddr_in name; + apr_os_sock_t listen_fd; +} listen_socket_t; + +typedef struct { + HMTX accept_mutex; + listen_socket_t listeners[1]; +} parent_info_t; + +static int master_main(); +static void spawn_child(int slot); +void ap_mpm_child_main(apr_pool_t *pconf); +static void set_signals(); + + +static int mpmt_os2_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +{ + char *listener_shm_name; + parent_info_t *parent_info; + ULONG rc; + pconf = _pconf; + ap_server_conf = s; + restart_pending = 0; + + DosSetMaxFH(ap_thread_limit * 2); + listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid()); + rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ); + is_parent_process = rc != 0; + ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid()); + + if (rc == 0) { + /* Child process */ + ap_listen_rec *lr; + int num_listeners = 0; + + ap_mpm_accept_mutex = parent_info->accept_mutex; + + /* Set up a default listener if necessary */ + if (ap_listeners == NULL) { + ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec)); + ap_listeners = lr; + apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC, + DEFAULT_HTTP_PORT, 0, s->process->pool); + apr_socket_create(&lr->sd, lr->bind_addr->family, + SOCK_STREAM, 0, s->process->pool); + } + + for (lr = ap_listeners; lr; lr = lr->next) { + apr_sockaddr_t *sa; + apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf); + apr_socket_addr_get(&sa, APR_LOCAL, lr->sd); + num_listeners++; + } + + DosFreeMem(parent_info); + + /* Do the work */ + ap_mpm_child_main(pconf); + + /* Outta here */ + return DONE; + } + else { + /* Parent process */ + int rc; + is_parent_process = TRUE; + + if (ap_setup_listeners(ap_server_conf) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00200) + "no listening sockets available, shutting down"); + return !OK; + } + + ap_log_pid(pconf, ap_pid_fname); + + rc = master_main(); + ++ap_my_generation; + ap_scoreboard_image->global->running_generation = ap_my_generation; + + if (rc != OK) { + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00201) + "caught %s, shutting down", + (rc == DONE) ? "SIGTERM" : "error"); + return rc; + } + } /* Parent process */ + + return OK; /* Restart */ +} + + + +/* Main processing of the parent process + * returns TRUE if restarting + */ +static int master_main() +{ + server_rec *s = ap_server_conf; + ap_listen_rec *lr; + parent_info_t *parent_info; + char *listener_shm_name; + int listener_num, num_listeners, slot; + ULONG rc; + + printf("%s \n", ap_get_server_description()); + set_signals(); + + if (ap_setup_listeners(ap_server_conf) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00202) + "no listening sockets available, shutting down"); + return !OK; + } + + /* Allocate a shared memory block for the array of listeners */ + for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) { + num_listeners++; + } + + listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid()); + rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name, + sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t), + PAG_READ|PAG_WRITE|PAG_COMMIT); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00203) + "failure allocating shared memory, shutting down"); + return !OK; + } + + /* Store the listener sockets in the shared memory area for our children to see */ + for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) { + apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd); + } + + /* Create mutex to prevent multiple child processes from detecting + * a connection with apr_poll() + */ + + rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00204) + "failure creating accept mutex, shutting down"); + return !OK; + } + + parent_info->accept_mutex = ap_mpm_accept_mutex; + + /* Allocate shared memory for scoreboard */ + if (ap_scoreboard_image == NULL) { + void *sb_mem; + rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname, + ap_calc_scoreboard_size(), + PAG_COMMIT|PAG_READ|PAG_WRITE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00205) + "unable to allocate shared memory for scoreboard , exiting"); + return !OK; + } + + ap_init_scoreboard(sb_mem); + } + + ap_scoreboard_image->global->restart_time = apr_time_now(); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00206) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00207) + "Server built: %s", ap_get_server_built()); + if (one_process) { + ap_scoreboard_image->parent[0].pid = getpid(); + ap_mpm_child_main(pconf); + return DONE; + } + + while (!restart_pending && !shutdown_pending) { + RESULTCODES proc_rc; + PID child_pid; + int active_children = 0; + + /* Count number of active children */ + for (slot=0; slot < HARD_SERVER_LIMIT; slot++) { + active_children += ap_scoreboard_image->parent[slot].pid != 0 && + !ap_scoreboard_image->parent[slot].quiescing; + } + + /* Spawn children if needed */ + for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) { + if (ap_scoreboard_image->parent[slot].pid == 0) { + spawn_child(slot); + active_children++; + } + } + + rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0); + + if (rc == 0) { + /* A child has terminated, remove its scoreboard entry & terminate if necessary */ + for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++); + + if (slot < HARD_SERVER_LIMIT) { + ap_scoreboard_image->parent[slot].pid = 0; + ap_scoreboard_image->parent[slot].quiescing = 0; + + if (proc_rc.codeTerminate == TC_EXIT) { + /* Child terminated normally, check its exit code and + * terminate server if child indicates a fatal error + */ + if (proc_rc.codeResult == APEXIT_CHILDFATAL) + break; + } + } + } else if (rc == ERROR_CHILD_NOT_COMPLETE) { + /* No child exited, lets sleep for a while.... */ + apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); + } + } + + /* Signal children to shut down, either gracefully or immediately */ + for (slot=0; slot<HARD_SERVER_LIMIT; slot++) { + kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM); + } + + DosFreeMem(parent_info); + return restart_pending ? OK : DONE; +} + + + +static void spawn_child(int slot) +{ + PPIB ppib; + PTIB ptib; + char fail_module[100]; + char progname[CCHMAXPATH]; + RESULTCODES proc_rc; + ULONG rc; + + ap_scoreboard_image->parent[slot].generation = ap_my_generation; + DosGetInfoBlocks(&ptib, &ppib); + DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname); + rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT, + ppib->pib_pchcmd, NULL, &proc_rc, progname); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00208) + "error spawning child, slot %d", slot); + } + + if (slot + 1 > ap_max_daemons_limit) { + ap_max_daemons_limit = slot + 1; + } + + ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate; +} + + + +/* Signal handling routines */ + +static void sig_term(int sig) +{ + shutdown_pending = 1; + signal(SIGTERM, SIG_DFL); +} + + + +static void sig_restart(int sig) +{ + if (sig == SIGUSR1) { + is_graceful = 1; + } + + restart_pending = 1; +} + + + +static void set_signals() +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_term; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00209) "sigaction(SIGTERM)"); + + if (sigaction(SIGINT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00210) "sigaction(SIGINT)"); + + sa.sa_handler = sig_restart; + + if (sigaction(SIGHUP, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00211) "sigaction(SIGHUP)"); + if (sigaction(SIGUSR1, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00212) "sigaction(SIGUSR1)"); +} + + + +/* Enquiry functions used get MPM status info */ + +static apr_status_t mpmt_os2_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = ap_max_daemons_limit; + break; + + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_DYNAMIC; + break; + + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = HARD_THREAD_LIMIT; + break; + + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + + case AP_MPMQ_GENERATION: + *result = ap_my_generation; + break; + + default: + *rv = APR_ENOTIMPL; + break; + } + + return OK; +} + + + + +static const char *mpmt_os2_get_name(void) +{ + return "mpmt_os2"; +} + + + + +/* Configuration handling stuff */ + +static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + one_process = ap_exists_config_define("ONE_PROCESS") || + ap_exists_config_define("DEBUG"); + is_graceful = 0; + ap_listen_pre_config(); + ap_daemons_to_start = DEFAULT_START_DAEMON; + ap_thread_limit = HARD_THREAD_LIMIT; + ap_extended_status = 0; + ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD; + ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD; + ap_sys_privileges_handlers(1); + + return OK; +} + + + +static int mpmt_os2_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; + + /* we want this only the first time around */ + if (restart_num++ == 0) { + startup = 1; + } + + if (ap_daemons_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00213) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00214) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (ap_min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00215) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", ap_min_spare_threads); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00216) + "MinSpareThreads of %d not allowed, increasing to 1", + ap_min_spare_threads); + } + ap_min_spare_threads = 1; + } + + return OK; +} + + + +static void mpmt_os2_hooks(apr_pool_t *p) +{ + ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(mpmt_os2_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(mpmt_os2_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(mpmt_os2_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(mpmt_os2_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + + + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + + + +static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_min_spare_threads = atoi(arg); + return NULL; +} + + + +static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_max_spare_threads = atoi(arg); + return NULL; +} + + + +static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg) +{ + return NULL; +} + + + +static const command_rec mpmt_os2_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup" ), +AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, + "Minimum number of idle children, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, + "Maximum number of idle children"), +AP_INIT_TAKE1("User", ignore_cmd, NULL, RSRC_CONF, + "Not applicable on this platform"), +AP_INIT_TAKE1("Group", ignore_cmd, NULL, RSRC_CONF, + "Not applicable on this platform"), +AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \ + "Not applicable on this platform"), +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_mpmt_os2) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + mpmt_os2_cmds, /* command apr_table_t */ + mpmt_os2_hooks, /* register_hooks */ +}; diff --git a/server/mpm/mpmt_os2/mpmt_os2_child.c b/server/mpm/mpmt_os2/mpmt_os2_child.c new file mode 100644 index 0000000..f405cd2 --- /dev/null +++ b/server/mpm/mpmt_os2/mpmt_os2_child.c @@ -0,0 +1,490 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define INCL_NOPMAPI +#define INCL_DOS +#define INCL_DOSERRORS + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "scoreboard.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "apr_portable.h" +#include "apr_poll.h" +#include "mpm_common.h" +#include "apr_strings.h" +#include <os2.h> +#include <process.h> + +APLOG_USE_MODULE(mpm_mpmt_os2); + +/* XXXXXX move these to header file private to this MPM */ + +/* We don't need many processes, + * they're only for redundancy in the event of a crash + */ +#define HARD_SERVER_LIMIT 10 + +/* Limit on the total number of threads per process + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 256 +#endif + +#define ID_FROM_CHILD_THREAD(c, t) ((c * HARD_THREAD_LIMIT) + t) + +typedef struct { + apr_pool_t *pconn; + apr_socket_t *conn_sd; +} worker_args_t; + +#define WORKTYPE_CONN 0 +#define WORKTYPE_EXIT 1 + +static apr_pool_t *pchild = NULL; +static int child_slot; +static int shutdown_pending = 0; +extern int ap_my_generation; +static int volatile is_graceful = 1; +HEV shutdown_event; /* signaled when this child is shutting down */ + +/* grab some MPM globals */ +extern int ap_min_spare_threads; +extern int ap_max_spare_threads; +extern HMTX ap_mpm_accept_mutex; + +static void worker_main(void *vpArg); +static void clean_child_exit(int code); +static void set_signals(); +static void server_maintenance(void *vpArg); + + +static void clean_child_exit(int code) +{ + if (pchild) { + apr_pool_destroy(pchild); + } + + exit(code); +} + + + +void ap_mpm_child_main(apr_pool_t *pconf) +{ + ap_listen_rec *lr = NULL; + int requests_this_child = 0; + int rv = 0; + unsigned long ulTimes; + int my_pid = getpid(); + ULONG rc, c; + HQUEUE workq; + apr_pollset_t *pollset; + int num_listeners; + TID server_maint_tid; + void *sb_mem; + + /* Stop Ctrl-C/Ctrl-Break signals going to child processes */ + DosSetSignalExceptionFocus(0, &ulTimes); + set_signals(); + + /* Create pool for child */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + + /* Create an event semaphore used to trigger other threads to shutdown */ + rc = DosCreateEventSem(NULL, &shutdown_event, 0, FALSE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00189) + "unable to create shutdown semaphore, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Gain access to the scoreboard. */ + rc = DosGetNamedSharedMem(&sb_mem, ap_scoreboard_fname, + PAG_READ|PAG_WRITE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00190) + "scoreboard not readable in child, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_calc_scoreboard_size(); + ap_init_scoreboard(sb_mem); + + /* Gain access to the accpet mutex */ + rc = DosOpenMutexSem(NULL, &ap_mpm_accept_mutex); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00191) + "accept mutex couldn't be accessed in child, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Find our pid in the scoreboard so we know what slot our parent allocated us */ + for (child_slot = 0; ap_scoreboard_image->parent[child_slot].pid != my_pid && child_slot < HARD_SERVER_LIMIT; child_slot++); + + if (child_slot == HARD_SERVER_LIMIT) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00192) + "child pid not found in scoreboard, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_my_generation = ap_scoreboard_image->parent[child_slot].generation; + memset(ap_scoreboard_image->servers[child_slot], 0, sizeof(worker_score) * HARD_THREAD_LIMIT); + + /* Set up an OS/2 queue for passing connections & termination requests + * to worker threads + */ + rc = DosCreateQueue(&workq, QUE_FIFO, apr_psprintf(pchild, "/queues/httpd/work.%d", my_pid)); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00193) + "unable to create work queue, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create initial pool of worker threads */ + for (c = 0; c < ap_min_spare_threads; c++) { +// ap_scoreboard_image->servers[child_slot][c].tid = _beginthread(worker_main, NULL, 128*1024, (void *)c); + } + + /* Start maintenance thread */ + server_maint_tid = _beginthread(server_maintenance, NULL, 32768, NULL); + + /* Set up poll */ + for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) { + num_listeners++; + } + + apr_pollset_create(&pollset, num_listeners, pchild, 0); + + for (lr = ap_listeners; lr != NULL; lr = lr->next) { + apr_pollfd_t pfd = { 0 }; + + pfd.desc_type = APR_POLL_SOCKET; + pfd.desc.s = lr->sd; + pfd.reqevents = APR_POLLIN; + pfd.client_data = lr; + apr_pollset_add(pollset, &pfd); + } + + /* Main connection accept loop */ + do { + apr_pool_t *pconn; + worker_args_t *worker_args; + int last_poll_idx = 0; + + apr_pool_create(&pconn, pchild); + apr_pool_tag(pconn, "transaction"); + worker_args = apr_palloc(pconn, sizeof(worker_args_t)); + worker_args->pconn = pconn; + + if (num_listeners == 1) { + rv = apr_socket_accept(&worker_args->conn_sd, ap_listeners->sd, pconn); + } else { + const apr_pollfd_t *poll_results; + apr_int32_t num_poll_results; + + rc = DosRequestMutexSem(ap_mpm_accept_mutex, SEM_INDEFINITE_WAIT); + + if (shutdown_pending) { + DosReleaseMutexSem(ap_mpm_accept_mutex); + break; + } + + rv = APR_FROM_OS_ERROR(rc); + + if (rv == APR_SUCCESS) { + rv = apr_pollset_poll(pollset, -1, &num_poll_results, &poll_results); + DosReleaseMutexSem(ap_mpm_accept_mutex); + } + + if (rv == APR_SUCCESS) { + if (last_poll_idx >= num_listeners) { + last_poll_idx = 0; + } + + lr = poll_results[last_poll_idx++].client_data; + rv = apr_socket_accept(&worker_args->conn_sd, lr->sd, pconn); + last_poll_idx++; + } + } + + if (rv != APR_SUCCESS) { + if (!APR_STATUS_IS_EINTR(rv)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00194) + "apr_socket_accept"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } else { + DosWriteQueue(workq, WORKTYPE_CONN, sizeof(worker_args_t), worker_args, 0); + requests_this_child++; + } + + if (ap_max_requests_per_child != 0 && requests_this_child >= ap_max_requests_per_child) + break; + } while (!shutdown_pending && ap_my_generation == ap_scoreboard_image->global->running_generation); + + ap_scoreboard_image->parent[child_slot].quiescing = 1; + DosPostEventSem(shutdown_event); + DosWaitThread(&server_maint_tid, DCWW_WAIT); + + if (is_graceful) { + char someleft; + + /* tell our worker threads to exit */ + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0); + } + } + + do { + someleft = 0; + + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + someleft = 1; + DosSleep(1000); + break; + } + } + } while (someleft); + } else { + DosPurgeQueue(workq); + + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + DosKillThread(ap_scoreboard_image->servers[child_slot][c].tid); + } + } + } + + apr_pool_destroy(pchild); +} + + + +void add_worker() +{ + int thread_slot; + int stacksize = ap_thread_stacksize == 0 ? 128*1024 : ap_thread_stacksize; + + /* Find a free thread slot */ + for (thread_slot=0; thread_slot < HARD_THREAD_LIMIT; thread_slot++) { + if (ap_scoreboard_image->servers[child_slot][thread_slot].status == SERVER_DEAD) { + ap_scoreboard_image->servers[child_slot][thread_slot].status = SERVER_STARTING; + ap_scoreboard_image->servers[child_slot][thread_slot].tid = + _beginthread(worker_main, NULL, stacksize, (void *)thread_slot); + break; + } + } +} + + + +ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec, + EXCEPTIONREGISTRATIONRECORD *pRegRec, + CONTEXTRECORD *pContext, + PVOID p) +{ + int c; + + if (pReportRec->fHandlerFlags & EH_NESTED_CALL) { + return XCPT_CONTINUE_SEARCH; + } + + if (pReportRec->ExceptionNum == XCPT_ACCESS_VIOLATION || + pReportRec->ExceptionNum == XCPT_INTEGER_DIVIDE_BY_ZERO) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00195) + "caught exception in worker thread, initiating child shutdown pid=%d", getpid()); + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].tid == _gettid()) { + ap_scoreboard_image->servers[child_slot][c].status = SERVER_DEAD; + break; + } + } + + /* Shut down process ASAP, it could be quite unhealthy & leaking resources */ + shutdown_pending = 1; + ap_scoreboard_image->parent[child_slot].quiescing = 1; + kill(getpid(), SIGHUP); + DosUnwindException(UNWIND_ALL, 0, 0); + } + + return XCPT_CONTINUE_SEARCH; +} + + + +static void worker_main(void *vpArg) +{ + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; + long conn_id; + conn_rec *current_conn; + apr_pool_t *pconn; + apr_allocator_t *allocator; + apr_bucket_alloc_t *bucket_alloc; + worker_args_t *worker_args; + HQUEUE workq; + PID owner; + int rc; + REQUESTDATA rd; + ULONG len; + BYTE priority; + int thread_slot = (int)vpArg; + EXCEPTIONREGISTRATIONRECORD reg_rec = { NULL, thread_exception_handler }; + ap_sb_handle_t *sbh; + + /* Trap exceptions in this thread so we don't take down the whole process */ + DosSetExceptionHandler( ®_rec ); + + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); + + rc = DosOpenQueue(&owner, &workq, + apr_psprintf(pchild, "/queues/httpd/work.%d", getpid())); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00196) + "unable to open work queue, exiting"); + ap_scoreboard_image->servers[child_slot][thread_slot].tid = 0; + } + + conn_id = ID_FROM_CHILD_THREAD(child_slot, thread_slot); + ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_READY, + NULL); + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + bucket_alloc = apr_bucket_alloc_create_ex(allocator); + + while (rc = DosReadQueue(workq, &rd, &len, (PPVOID)&worker_args, 0, DCWW_WAIT, &priority, NULLHANDLE), + rc == 0 && rd.ulData != WORKTYPE_EXIT) { + pconn = worker_args->pconn; + ap_create_sb_handle(&sbh, pconn, child_slot, thread_slot); + current_conn = ap_run_create_connection(pconn, ap_server_conf, + worker_args->conn_sd, conn_id, + sbh, bucket_alloc); + + if (current_conn) { + current_conn->current_thread = thd; + ap_process_connection(current_conn, worker_args->conn_sd); + ap_lingering_close(current_conn); + } + + apr_pool_destroy(pconn); + ap_update_child_status_from_indexes(child_slot, thread_slot, + SERVER_READY, NULL); + } + + ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_DEAD, + NULL); + + apr_bucket_alloc_destroy(bucket_alloc); + apr_allocator_destroy(allocator); +} + + + +static void server_maintenance(void *vpArg) +{ + int num_idle, num_needed; + ULONG num_pending = 0; + int threadnum; + HQUEUE workq; + ULONG rc; + PID owner; + + rc = DosOpenQueue(&owner, &workq, + apr_psprintf(pchild, "/queues/httpd/work.%d", getpid())); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00197) + "unable to open work queue in maintenance thread"); + return; + } + + do { + for (num_idle=0, threadnum=0; threadnum < HARD_THREAD_LIMIT; threadnum++) { + num_idle += ap_scoreboard_image->servers[child_slot][threadnum].status == SERVER_READY; + } + + DosQueryQueue(workq, &num_pending); + num_needed = ap_min_spare_threads - num_idle + num_pending; + + if (num_needed > 0) { + for (threadnum=0; threadnum < num_needed; threadnum++) { + add_worker(); + } + } + + if (num_idle - num_pending > ap_max_spare_threads) { + DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0); + } + } while (DosWaitEventSem(shutdown_event, 500) == ERROR_TIMEOUT); +} + + + +/* Signal handling routines */ + +static void sig_term(int sig) +{ + shutdown_pending = 1; + is_graceful = 0; + signal(SIGTERM, SIG_DFL); +} + + + +static void sig_hup(int sig) +{ + shutdown_pending = 1; + is_graceful = 1; +} + + + +static void set_signals() +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_term; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00198) "sigaction(SIGTERM)"); + + sa.sa_handler = sig_hup; + + if (sigaction(SIGHUP, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00199) "sigaction(SIGHUP)"); +} diff --git a/server/mpm/netware/mpm_default.h b/server/mpm/netware/mpm_default.h new file mode 100644 index 0000000..f7783ce --- /dev/null +++ b/server/mpm/netware/mpm_default.h @@ -0,0 +1,78 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file netware/mpm_default.h + * @brief Defaults for Netware MPM + * + * @defgroup APACHE_MPM_NETWARE Netware MPM + * @ingroup APACHE_INTERNAL + * @{ + */ +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Limit on the threads per process. Clients will be locked out if more than + * this * HARD_SERVER_LIMIT are needed. + * + * We keep this for one reason it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 2048 +#endif + +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 50 +#endif + +/* Number of threads to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_THREADS +#define DEFAULT_START_THREADS DEFAULT_THREADS_PER_CHILD +#endif + +/* Maximum number of *free* threads --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_THREADS +#define DEFAULT_MAX_FREE_THREADS 100 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_THREADS +#define DEFAULT_MIN_FREE_THREADS 10 +#endif + +/* + * Interval, in microseconds, between scoreboard maintenance. + */ +#ifndef SCOREBOARD_MAINTENANCE_INTERVAL +#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000 +#endif + +/* Default stack size allocated for each worker thread. + */ +#ifndef DEFAULT_THREAD_STACKSIZE +#define DEFAULT_THREAD_STACKSIZE 65536 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/netware/mpm_netware.c b/server/mpm/netware/mpm_netware.c new file mode 100644 index 0000000..e89fdef --- /dev/null +++ b/server/mpm/netware/mpm_netware.c @@ -0,0 +1,1365 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * httpd.c: simple http daemon for answering WWW file requests + * + * + * 03-21-93 Rob McCool wrote original code (up to NCSA HTTPd 1.3) + * + * 03-06-95 blong + * changed server number for child-alone processes to 0 and changed name + * of processes + * + * 03-10-95 blong + * Added numerous speed hacks proposed by Robert S. Thau (rst@ai.mit.edu) + * including set group before fork, and call gettime before to fork + * to set up libraries. + * + * 04-14-95 rst / rh + * Brandon's code snarfed from NCSA 1.4, but tinkered to work with the + * Apache server, and also to have child processes do accept() directly. + * + * April-July '95 rst + * Extensive rework for Apache. + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_tables.h" +#include "apr_getopt.h" +#include "apr_thread_mutex.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifndef USE_WINSOCK +#include <sys/select.h> +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "scoreboard.h" +#include "ap_mpm.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "ap_mmn.h" + +#ifdef HAVE_TIME_H +#include <time.h> +#endif + +#include <signal.h> + +#include <netware.h> +#include <nks/netware.h> +#include <library.h> +#include <screen.h> + +int nlmUnloadSignaled(int wait); + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef HARD_SERVER_LIMIT +#define HARD_SERVER_LIMIT 1 +#endif + +#define WORKER_DEAD SERVER_DEAD +#define WORKER_STARTING SERVER_STARTING +#define WORKER_READY SERVER_READY +#define WORKER_IDLE_KILL SERVER_IDLE_KILL + +#define MPM_HARD_LIMITS_FILE "/mpm_default.h" + +/* *Non*-shared http_main globals... */ + +static int ap_threads_per_child=0; /* Worker threads per child */ +static int ap_threads_to_start=0; +static int ap_threads_min_free=0; +static int ap_threads_max_free=0; +static int ap_threads_limit=0; +static int mpm_state = AP_MPMQ_STARTING; + +/* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across SIGWINCH restarts. We use this + * value to optimize routines that have to scan the entire scoreboard. + */ +static int ap_max_workers_limit = -1; + +int hold_screen_on_exit = 0; /* Indicates whether the screen should be held open */ + +static fd_set listenfds; +static int listenmaxfd; + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pmain; /* Pool for httpd child stuff */ + +static pid_t ap_my_pid; /* it seems silly to call getpid all the time */ +static char *ap_my_addrspace = NULL; + +static int die_now = 0; + +/* Keep track of the number of worker threads currently active */ +static unsigned long worker_thread_count; +static int request_count; + +/* Structure used to register/deregister a console handler with the OS */ +static int InstallConsoleHandler(void); +static void RemoveConsoleHandler(void); +static int CommandLineInterpreter(scr_t screenID, const char *commandLine); +static CommandParser_t ConsoleHandler = {0, NULL, 0}; +#define HANDLEDCOMMAND 0 +#define NOTMYCOMMAND 1 + +static int show_settings = 0; + +//#define DBINFO_ON +//#define DBPRINT_ON +#ifdef DBPRINT_ON +#define DBPRINT0(s) printf(s) +#define DBPRINT1(s,v1) printf(s,v1) +#define DBPRINT2(s,v1,v2) printf(s,v1,v2) +#else +#define DBPRINT0(s) +#define DBPRINT1(s,v1) +#define DBPRINT2(s,v1,v2) +#endif + +/* volatile just in case */ +static int volatile shutdown_pending; +static int volatile restart_pending; +static int volatile is_graceful; +static int volatile wait_to_finish=1; +static ap_generation_t volatile ap_my_generation=0; + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans, + apr_bucket_alloc_t *bucket_alloc) __attribute__ ((noreturn)); +static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans, + apr_bucket_alloc_t *bucket_alloc) +{ + apr_bucket_alloc_destroy(bucket_alloc); + if (!shutdown_pending) { + apr_pool_destroy(ptrans); + } + + atomic_dec (&worker_thread_count); + if (worker_num >=0) + ap_update_child_status_from_indexes(0, worker_num, WORKER_DEAD, + (request_rec *) NULL); + NXThreadExit((void*)&code); +} + +/* proper cleanup when returning from ap_mpm_run() */ +static void mpm_main_cleanup(void) +{ + if (pmain) { + apr_pool_destroy(pmain); + } +} + +static int netware_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch(query_code){ + case AP_MPMQ_MAX_DAEMON_USED: + *result = 1; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = HARD_THREAD_LIMIT; + break; + case AP_MPMQ_MAX_THREADS: + *result = ap_threads_limit; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = ap_threads_min_free; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = ap_threads_max_free; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = 1; + break; + case AP_MPMQ_MPM_STATE: + *result = mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = ap_my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static const char *netware_get_name(void) +{ + return "NetWare"; +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static void mpm_term(void) +{ + RemoveConsoleHandler(); + wait_to_finish = 0; + NXThreadYield(); +} + +static void sig_term(int sig) +{ + if (shutdown_pending == 1) { + /* Um, is this _probably_ not an error, if the user has + * tried to do a shutdown twice quickly, so we won't + * worry about reporting it. + */ + return; + } + shutdown_pending = 1; + + DBPRINT0 ("waiting for threads\n"); + while (wait_to_finish) { + apr_thread_yield(); + } + DBPRINT0 ("goodbye\n"); +} + +/* restart() is the signal handler for SIGHUP and SIGWINCH + * in the parent process, unless running in ONE_PROCESS mode + */ +static void restart(void) +{ + if (restart_pending == 1) { + /* Probably not an error - don't bother reporting it */ + return; + } + restart_pending = 1; + is_graceful = 1; +} + +static void set_signals(void) +{ + apr_signal(SIGTERM, sig_term); + apr_signal(SIGABRT, sig_term); +} + +int nlmUnloadSignaled(int wait) +{ + shutdown_pending = 1; + + if (wait) { + while (wait_to_finish) { + NXThreadYield(); + } + } + + return 0; +} + +/***************************************************************** + * Child process main loop. + * The following vars are static to avoid getting clobbered by longjmp(); + * they are really private to child_main. + */ + + +#define MAX_WB_RETRIES 3 +#ifdef DBINFO_ON +static int would_block = 0; +static int retry_success = 0; +static int retry_fail = 0; +static int avg_retries = 0; +#endif + +/*static */ +void worker_main(void *arg) +{ + ap_listen_rec *lr, *first_lr, *last_lr = NULL; + apr_pool_t *ptrans; + apr_allocator_t *allocator; + apr_bucket_alloc_t *bucket_alloc; + conn_rec *current_conn; + apr_status_t stat = APR_EINIT; + ap_sb_handle_t *sbh; + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; + + int my_worker_num = (int)arg; + apr_socket_t *csd = NULL; + int requests_this_child = 0; + apr_socket_t *sd = NULL; + fd_set main_fds; + + int sockdes; + int srv; + struct timeval tv; + int wouldblock_retry; + + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pmain); + + tv.tv_sec = 1; + tv.tv_usec = 0; + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + + apr_pool_create_ex(&ptrans, pmain, NULL, allocator); + apr_allocator_owner_set(allocator, ptrans); + apr_pool_tag(ptrans, "transaction"); + + bucket_alloc = apr_bucket_alloc_create_ex(allocator); + + atomic_inc (&worker_thread_count); + + while (!die_now) { + /* + * (Re)initialize this child to a pre-connection state. + */ + current_conn = NULL; + apr_pool_clear(ptrans); + + if ((ap_max_requests_per_child > 0 + && requests_this_child++ >= ap_max_requests_per_child)) { + DBPRINT1 ("\n**Thread slot %d is shutting down", my_worker_num); + clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); + } + + ap_update_child_status_from_indexes(0, my_worker_num, WORKER_READY, + (request_rec *) NULL); + + /* + * Wait for an acceptable connection to arrive. + */ + + for (;;) { + if (shutdown_pending || restart_pending || (ap_scoreboard_image->servers[0][my_worker_num].status == WORKER_IDLE_KILL)) { + DBPRINT1 ("\nThread slot %d is shutting down\n", my_worker_num); + clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); + } + + /* Check the listen queue on all sockets for requests */ + memcpy(&main_fds, &listenfds, sizeof(fd_set)); + srv = select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv); + + if (srv <= 0) { + if (srv < 0) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00217) + "select() failed on listen socket"); + apr_thread_yield(); + } + continue; + } + + /* remember the last_lr we searched last time around so that + we don't end up starving any particular listening socket */ + if (last_lr == NULL) { + lr = ap_listeners; + } + else { + lr = last_lr->next; + if (!lr) + lr = ap_listeners; + } + first_lr = lr; + do { + apr_os_sock_get(&sockdes, lr->sd); + if (FD_ISSET(sockdes, &main_fds)) + goto got_listener; + lr = lr->next; + if (!lr) + lr = ap_listeners; + } while (lr != first_lr); + /* if we get here, something unexpected happened. Go back + into the select state and try again. + */ + continue; + got_listener: + last_lr = lr; + sd = lr->sd; + + wouldblock_retry = MAX_WB_RETRIES; + + while (wouldblock_retry) { + if ((stat = apr_socket_accept(&csd, sd, ptrans)) == APR_SUCCESS) { + break; + } + else { + /* if the error is a wouldblock then maybe we were too + quick try to pull the next request from the listen + queue. Try a few more times then return to our idle + listen state. */ + if (!APR_STATUS_IS_EAGAIN(stat)) { + break; + } + + if (wouldblock_retry--) { + apr_thread_yield(); + } + } + } + + /* If we got a new socket, set it to non-blocking mode and process + it. Otherwise handle the error. */ + if (stat == APR_SUCCESS) { + apr_socket_opt_set(csd, APR_SO_NONBLOCK, 0); +#ifdef DBINFO_ON + if (wouldblock_retry < MAX_WB_RETRIES) { + retry_success++; + avg_retries += (MAX_WB_RETRIES-wouldblock_retry); + } +#endif + break; /* We have a socket ready for reading */ + } + else { +#ifdef DBINFO_ON + if (APR_STATUS_IS_EAGAIN(stat)) { + would_block++; + retry_fail++; + } + else if ( +#else + if (APR_STATUS_IS_EAGAIN(stat) || +#endif + APR_STATUS_IS_ECONNRESET(stat) || + APR_STATUS_IS_ETIMEDOUT(stat) || + APR_STATUS_IS_EHOSTUNREACH(stat) || + APR_STATUS_IS_ENETUNREACH(stat)) { + ; + } +#ifdef USE_WINSOCK + else if (APR_STATUS_IS_ENETDOWN(stat)) { + /* + * When the network layer has been shut down, there + * is not much use in simply exiting: the parent + * would simply re-create us (and we'd fail again). + * Use the CHILDFATAL code to tear the server down. + * @@@ Martin's idea for possible improvement: + * A different approach would be to define + * a new APEXIT_NETDOWN exit code, the reception + * of which would make the parent shutdown all + * children, then idle-loop until it detected that + * the network is up again, and restart the children. + * Ben Hyde noted that temporary ENETDOWN situations + * occur in mobile IP. + */ + ap_log_error(APLOG_MARK, APLOG_EMERG, stat, ap_server_conf, APLOGNO(00218) + "apr_socket_accept: giving up."); + clean_child_exit(APEXIT_CHILDFATAL, my_worker_num, ptrans, + bucket_alloc); + } +#endif + else { + ap_log_error(APLOG_MARK, APLOG_ERR, stat, ap_server_conf, APLOGNO(00219) + "apr_socket_accept: (client socket)"); + clean_child_exit(1, my_worker_num, ptrans, bucket_alloc); + } + } + } + + ap_create_sb_handle(&sbh, ptrans, 0, my_worker_num); + /* + * We now have a connection, so set it up with the appropriate + * socket options, file descriptors, and read/write buffers. + */ + current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, + my_worker_num, sbh, + bucket_alloc); + if (current_conn) { + current_conn->current_thread = thd; + ap_process_connection(current_conn, csd); + ap_lingering_close(current_conn); + } + request_count++; + } + clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); +} + + +static int make_child(server_rec *s, int slot) +{ + int tid; + int err=0; + NXContext_t ctx; + + if (slot + 1 > ap_max_workers_limit) { + ap_max_workers_limit = slot + 1; + } + + ap_update_child_status_from_indexes(0, slot, WORKER_STARTING, + (request_rec *) NULL); + + if (ctx = NXContextAlloc((void (*)(void *)) worker_main, (void*)slot, NX_PRIO_MED, ap_thread_stacksize, NX_CTX_NORMAL, &err)) { + char threadName[32]; + + sprintf (threadName, "Apache_Worker %d", slot); + NXContextSetName(ctx, threadName); + err = NXThreadCreate(ctx, NX_THR_BIND_CONTEXT, &tid); + if (err) { + NXContextFree (ctx); + } + } + + if (err) { + /* create thread didn't succeed. Fix the scoreboard or else + * it will say SERVER_STARTING forever and ever + */ + ap_update_child_status_from_indexes(0, slot, WORKER_DEAD, + (request_rec *) NULL); + + /* In case system resources are maxxed out, we don't want + Apache running away with the CPU trying to fork over and + over and over again. */ + apr_thread_yield(); + + return -1; + } + + ap_scoreboard_image->servers[0][slot].tid = tid; + + return 0; +} + + +/* start up a bunch of worker threads */ +static void startup_workers(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < ap_threads_limit; ++i) { + if (ap_scoreboard_image->servers[0][i].status != WORKER_DEAD) { + continue; + } + if (make_child(ap_server_conf, i) < 0) { + break; + } + --number_to_start; + } +} + + +/* + * 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 (64) +#endif +static int hold_off_on_exponential_spawning; + +static void perform_idle_server_maintenance(apr_pool_t *p) +{ + int i; + int idle_count; + worker_score *ws; + int free_length; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead; + int total_non_dead; + + /* initialize the free_list */ + free_length = 0; + + idle_count = 0; + last_non_dead = -1; + total_non_dead = 0; + + for (i = 0; i < ap_threads_limit; ++i) { + int status; + + if (i >= ap_max_workers_limit && free_length == idle_spawn_rate) + break; + ws = &ap_scoreboard_image->servers[0][i]; + status = ws->status; + if (status == WORKER_DEAD) { + /* try to keep children numbers as low as possible */ + if (free_length < idle_spawn_rate) { + free_slots[free_length] = i; + ++free_length; + } + } + else if (status == WORKER_IDLE_KILL) { + /* If it is already marked to die, skip it */ + continue; + } + else { + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (status <= WORKER_READY) { + ++ idle_count; + } + + ++total_non_dead; + last_non_dead = i; + } + } + DBPRINT2("Total: %d Idle Count: %d \r", total_non_dead, idle_count); + ap_max_workers_limit = last_non_dead + 1; + if (idle_count > ap_threads_max_free) { + /* kill off one child... we use the pod because that'll cause it to + * shut down gracefully, in case it happened to pick up a request + * while we were counting + */ + idle_spawn_rate = 1; + ap_update_child_status_from_indexes(0, last_non_dead, WORKER_IDLE_KILL, + (request_rec *) NULL); + DBPRINT1("\nKilling idle thread: %d\n", last_non_dead); + } + else if (idle_count < ap_threads_min_free) { + /* terminate the free list */ + if (free_length == 0) { + /* only report this condition once */ + static int reported = 0; + + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00220) + "server reached MaxRequestWorkers setting, consider" + " raising the MaxRequestWorkers setting"); + reported = 1; + } + idle_spawn_rate = 1; + } + else { + if (idle_spawn_rate >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00221) + "server seems busy, (you may need " + "to increase StartServers, or Min/MaxSpareServers), " + "spawning %d children, there are %d idle, and " + "%d total children", idle_spawn_rate, + idle_count, total_non_dead); + } + DBPRINT0("\n"); + for (i = 0; i < free_length; ++i) { + DBPRINT1("Spawning additional thread slot: %d\n", free_slots[i]); + make_child(ap_server_conf, free_slots[i]); + } + /* 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; + } + else if (idle_spawn_rate < MAX_SPAWN_RATE) { + idle_spawn_rate *= 2; + } + } + } + else { + idle_spawn_rate = 1; + } +} + +static void display_settings() +{ + int status_array[SERVER_NUM_STATUS]; + int i, status, total=0; + int reqs = request_count; +#ifdef DBINFO_ON + int wblock = would_block; + + would_block = 0; +#endif + + request_count = 0; + + ClearScreen (getscreenhandle()); + printf("%s \n", ap_get_server_description()); + + for (i=0;i<SERVER_NUM_STATUS;i++) { + status_array[i] = 0; + } + + for (i = 0; i < ap_threads_limit; ++i) { + status = (ap_scoreboard_image->servers[0][i]).status; + status_array[status]++; + } + + for (i=0;i<SERVER_NUM_STATUS;i++) { + switch(i) + { + case SERVER_DEAD: + printf ("Available:\t%d\n", status_array[i]); + break; + case SERVER_STARTING: + printf ("Starting:\t%d\n", status_array[i]); + break; + case SERVER_READY: + printf ("Ready:\t\t%d\n", status_array[i]); + break; + case SERVER_BUSY_READ: + printf ("Busy:\t\t%d\n", status_array[i]); + break; + case SERVER_BUSY_WRITE: + printf ("Busy Write:\t%d\n", status_array[i]); + break; + case SERVER_BUSY_KEEPALIVE: + printf ("Busy Keepalive:\t%d\n", status_array[i]); + break; + case SERVER_BUSY_LOG: + printf ("Busy Log:\t%d\n", status_array[i]); + break; + case SERVER_BUSY_DNS: + printf ("Busy DNS:\t%d\n", status_array[i]); + break; + case SERVER_CLOSING: + printf ("Closing:\t%d\n", status_array[i]); + break; + case SERVER_GRACEFUL: + printf ("Restart:\t%d\n", status_array[i]); + break; + case SERVER_IDLE_KILL: + printf ("Idle Kill:\t%d\n", status_array[i]); + break; + default: + printf ("Unknown Status:\t%d\n", status_array[i]); + break; + } + if (i != SERVER_DEAD) + total+=status_array[i]; + } + printf ("Total Running:\t%d\tout of: \t%d\n", total, ap_threads_limit); + printf ("Requests per interval:\t%d\n", reqs); + +#ifdef DBINFO_ON + printf ("Would blocks:\t%d\n", wblock); + printf ("Successful retries:\t%d\n", retry_success); + printf ("Failed retries:\t%d\n", retry_fail); + printf ("Avg retries:\t%d\n", retry_success == 0 ? 0 : avg_retries / retry_success); +#endif +} + +static void show_server_data() +{ + ap_listen_rec *lr; + module **m; + + printf("%s\n", ap_get_server_description()); + if (ap_my_addrspace && (ap_my_addrspace[0] != 'O') && (ap_my_addrspace[1] != 'S')) + printf(" Running in address space %s\n", ap_my_addrspace); + + + /* Display listening ports */ + printf(" Listening on port(s):"); + lr = ap_listeners; + do { + printf(" %d", lr->bind_addr->port); + lr = lr->next; + } while (lr && lr != ap_listeners); + + /* Display dynamic modules loaded */ + printf("\n"); + for (m = ap_loaded_modules; *m != NULL; m++) { + if (((module*)*m)->dynamic_load_handle) { + printf(" Loaded dynamic module %s\n", ((module*)*m)->name); + } + } +} + + +static int setup_listeners(server_rec *s) +{ + ap_listen_rec *lr; + int sockdes; + + if (ap_setup_listeners(s) < 1 ) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00222) + "no listening sockets available, shutting down"); + return -1; + } + + listenmaxfd = -1; + FD_ZERO(&listenfds); + for (lr = ap_listeners; lr; lr = lr->next) { + apr_os_sock_get(&sockdes, lr->sd); + FD_SET(sockdes, &listenfds); + if (sockdes > listenmaxfd) { + listenmaxfd = sockdes; + } + } + return 0; +} + +static int shutdown_listeners() +{ + ap_listen_rec *lr; + + for (lr = ap_listeners; lr; lr = lr->next) { + apr_socket_close(lr->sd); + } + ap_listeners = NULL; + return 0; +} + +/***************************************************************** + * Executive routines. + */ + +static int netware_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +{ + apr_status_t status=0; + + pconf = _pconf; + ap_server_conf = s; + + if (setup_listeners(s)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, status, s, APLOGNO(00223) + "no listening sockets available, shutting down"); + return !OK; + } + + restart_pending = shutdown_pending = 0; + worker_thread_count = 0; + + if (!is_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_NOT_SHARED) != OK) { + return !OK; + } + } + + /* Only set slot 0 since that is all NetWare will ever have. */ + ap_scoreboard_image->parent[0].pid = getpid(); + ap_scoreboard_image->parent[0].generation = ap_my_generation; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[0].pid, + ap_my_generation, + 0, + MPM_CHILD_STARTED); + + set_signals(); + + apr_pool_create(&pmain, pconf); + apr_pool_tag(pmain, "pmain"); + ap_run_child_init(pmain, ap_server_conf); + + if (ap_threads_max_free < ap_threads_min_free + 1) /* Don't thrash... */ + ap_threads_max_free = ap_threads_min_free + 1; + request_count = 0; + + startup_workers(ap_threads_to_start); + + /* Allow the Apache screen to be closed normally on exit() only if it + has not been explicitly forced to close on exit(). (ie. the -E flag + was specified at startup) */ + if (hold_screen_on_exit > 0) { + hold_screen_on_exit = 0; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00224) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00225) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + show_server_data(); + + mpm_state = AP_MPMQ_RUNNING; + while (!restart_pending && !shutdown_pending) { + perform_idle_server_maintenance(pconf); + if (show_settings) + display_settings(); + apr_thread_yield(); + apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); + } + mpm_state = AP_MPMQ_STOPPING; + + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[0].pid, + ap_my_generation, + 0, + MPM_CHILD_EXITED); + + /* Shutdown the listen sockets so that we don't get stuck in a blocking call. + shutdown_listeners();*/ + + if (shutdown_pending) { /* Got an unload from the console */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00226) + "caught SIGTERM, shutting down"); + + while (worker_thread_count > 0) { + printf ("\rShutdown pending. Waiting for %lu thread(s) to terminate...", + worker_thread_count); + apr_thread_yield(); + } + + mpm_main_cleanup(); + return DONE; + } + else { /* the only other way out is a restart */ + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++ap_my_generation; + ap_scoreboard_image->global->running_generation = ap_my_generation; + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00227) + "Graceful restart requested, doing restart"); + + /* Wait for all of the threads to terminate before initiating the restart */ + while (worker_thread_count > 0) { + printf ("\rRestart pending. Waiting for %lu thread(s) to terminate...", + worker_thread_count); + apr_thread_yield(); + } + printf ("\nRestarting...\n"); + } + + mpm_main_cleanup(); + return OK; +} + +static int netware_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) +{ + char *addrname = NULL; + + mpm_state = AP_MPMQ_STARTING; + + is_graceful = 0; + ap_my_pid = getpid(); + addrname = getaddressspacename (NULL, NULL); + if (addrname) { + ap_my_addrspace = apr_pstrdup (p, addrname); + free (addrname); + } + +#ifndef USE_WINSOCK + /* The following call has been moved to the mod_nw_ssl pre-config handler */ + ap_listen_pre_config(); +#endif + + ap_threads_to_start = DEFAULT_START_THREADS; + ap_threads_min_free = DEFAULT_MIN_FREE_THREADS; + ap_threads_max_free = DEFAULT_MAX_FREE_THREADS; + ap_threads_limit = HARD_THREAD_LIMIT; + ap_extended_status = 0; + + /* override core's default thread stacksize */ + ap_thread_stacksize = DEFAULT_THREAD_STACKSIZE; + + return OK; +} + +static int netware_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; + + /* we want this only the first time around */ + if (restart_num++ == 0) { + startup = 1; + } + + if (ap_threads_limit > HARD_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00228) + "WARNING: MaxThreads of %d exceeds compile-time " + "limit of %d threads, decreasing to %d. " + "To increase, please see the HARD_THREAD_LIMIT " + "define in server/mpm/netware%s.", + ap_threads_limit, HARD_THREAD_LIMIT, HARD_THREAD_LIMIT, + MPM_HARD_LIMITS_FILE); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00229) + "MaxThreads of %d exceeds compile-time limit " + "of %d, decreasing to match", + ap_threads_limit, HARD_THREAD_LIMIT); + } + ap_threads_limit = HARD_THREAD_LIMIT; + } + else if (ap_threads_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00230) + "WARNING: MaxThreads of %d not allowed, " + "increasing to 1.", ap_threads_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02661) + "MaxThreads of %d not allowed, increasing to 1", + ap_threads_limit); + } + ap_threads_limit = 1; + } + + /* ap_threads_to_start > ap_threads_limit effectively checked in + * call to startup_workers(ap_threads_to_start) in ap_mpm_run() + */ + if (ap_threads_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00231) + "WARNING: StartThreads of %d not allowed, " + "increasing to 1.", ap_threads_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00232) + "StartThreads of %d not allowed, increasing to 1", + ap_threads_to_start); + } + ap_threads_to_start = 1; + } + + if (ap_threads_min_free < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00233) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", ap_threads_min_free); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00234) + "MinSpareThreads of %d not allowed, increasing to 1", + ap_threads_min_free); + } + ap_threads_min_free = 1; + } + + /* ap_threads_max_free < ap_threads_min_free + 1 checked in ap_mpm_run() */ + + return OK; +} + +static void netware_mpm_hooks(apr_pool_t *p) +{ + /* Run the pre-config hook after core's so that it can override the + * default setting of ThreadStackSize for NetWare. + */ + static const char * const predecessors[] = {"core.c", NULL}; + + ap_hook_pre_config(netware_pre_config, predecessors, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(netware_check_config, NULL, NULL, APR_HOOK_MIDDLE); + //ap_hook_post_config(netware_post_config, NULL, NULL, 0); + //ap_hook_child_init(netware_child_init, NULL, NULL, APR_HOOK_MIDDLE); + //ap_hook_open_logs(netware_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + ap_hook_mpm(netware_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(netware_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(netware_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +static void netware_rewrite_args(process_rec *process) +{ + char *def_server_root; + char optbuf[3]; + const char *opt_arg; + apr_getopt_t *opt; + apr_array_header_t *mpm_new_argv; + + + atexit (mpm_term); + InstallConsoleHandler(); + + /* Make sure to hold the Apache screen open if exit() is called */ + hold_screen_on_exit = 1; + + /* Rewrite process->argv[]; + * + * add default -d serverroot from the path of this executable + * + * The end result will look like: + * The -d serverroot default from the running executable + */ + if (process->argc > 0) { + char *s = apr_pstrdup (process->pconf, process->argv[0]); + if (s) { + int i, len = strlen(s); + + for (i=len; i; i--) { + if (s[i] == '\\' || s[i] == '/') { + s[i] = '\0'; + apr_filepath_merge(&def_server_root, NULL, s, + APR_FILEPATH_TRUENAME, process->pool); + break; + } + } + /* Use process->pool so that the rewritten argv + * lasts for the lifetime of the server process, + * because pconf will be destroyed after the + * initial pre-flight of the config parser. + */ + mpm_new_argv = apr_array_make(process->pool, process->argc + 2, + sizeof(const char *)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + *(const char **)apr_array_push(mpm_new_argv) = "-d"; + *(const char **)apr_array_push(mpm_new_argv) = def_server_root; + + optbuf[0] = '-'; + optbuf[2] = '\0'; + apr_getopt_init(&opt, process->pool, process->argc, process->argv); + while (apr_getopt(opt, AP_SERVER_BASEARGS"n:", optbuf + 1, &opt_arg) == APR_SUCCESS) { + switch (optbuf[1]) { + case 'n': + if (opt_arg) { + renamescreen(opt_arg); + } + break; + case 'E': + /* Don't need to hold the screen open if the output is going to a file */ + hold_screen_on_exit = -1; + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + + if (opt_arg) { + *(const char **)apr_array_push(mpm_new_argv) = opt_arg; + } + break; + } + } + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *) mpm_new_argv->elts; + } + } +} + +static int CommandLineInterpreter(scr_t screenID, const char *commandLine) +{ + char *szCommand = "APACHE2 "; + int iCommandLen = 8; + char szcommandLine[256]; + char *pID; + screenID = screenID; + + + if (commandLine == NULL) + return NOTMYCOMMAND; + if (strlen(commandLine) <= strlen(szCommand)) + return NOTMYCOMMAND; + + apr_cpystrn(szcommandLine, commandLine, sizeof(szcommandLine)); + + /* All added commands begin with "APACHE2 " */ + + if (!strnicmp(szCommand, szcommandLine, iCommandLen)) { + ActivateScreen (getscreenhandle()); + + /* If an instance id was not given but the nlm is loaded in + protected space, then the command belongs to the + OS address space instance to pass it on. */ + pID = strstr (szcommandLine, "-p"); + if ((pID == NULL) && nlmisloadedprotected()) + return NOTMYCOMMAND; + + /* If we got an instance id but it doesn't match this + instance of the nlm, pass it on. */ + if (pID) { + pID = &pID[2]; + while (*pID && (*pID == ' ')) + pID++; + } + if (pID && ap_my_addrspace && strnicmp(pID, ap_my_addrspace, strlen(ap_my_addrspace))) + return NOTMYCOMMAND; + + /* If we have determined that this command belongs to this + instance of the nlm, then handle it. */ + if (!strnicmp("RESTART",&szcommandLine[iCommandLen],3)) { + printf("Restart Requested...\n"); + restart(); + } + else if (!strnicmp("VERSION",&szcommandLine[iCommandLen],3)) { + printf("Server version: %s\n", ap_get_server_description()); + printf("Server built: %s\n", ap_get_server_built()); + } + else if (!strnicmp("MODULES",&szcommandLine[iCommandLen],3)) { + ap_show_modules(); + } + else if (!strnicmp("DIRECTIVES",&szcommandLine[iCommandLen],3)) { + ap_show_directives(); + } + else if (!strnicmp("SHUTDOWN",&szcommandLine[iCommandLen],3)) { + printf("Shutdown Requested...\n"); + shutdown_pending = 1; + } + else if (!strnicmp("SETTINGS",&szcommandLine[iCommandLen],3)) { + if (show_settings) { + show_settings = 0; + ClearScreen (getscreenhandle()); + show_server_data(); + } + else { + show_settings = 1; + display_settings(); + } + } + else { + show_settings = 0; + if (strnicmp("HELP",&szcommandLine[iCommandLen],3)) + printf("Unknown APACHE2 command %s\n", &szcommandLine[iCommandLen]); + printf("Usage: APACHE2 [command] [-p <instance ID>]\n"); + printf("Commands:\n"); + printf("\tDIRECTIVES - Show directives\n"); + printf("\tHELP - Display this help information\n"); + printf("\tMODULES - Show a list of the loaded modules\n"); + printf("\tRESTART - Reread the configuration file and restart Apache\n"); + printf("\tSETTINGS - Show current thread status\n"); + printf("\tSHUTDOWN - Shutdown Apache\n"); + printf("\tVERSION - Display the server version information\n"); + } + + /* Tell NetWare we handled the command */ + return HANDLEDCOMMAND; + } + + /* Tell NetWare that the command isn't mine */ + return NOTMYCOMMAND; +} + +static int InstallConsoleHandler(void) +{ + /* Our command line handler interfaces the system operator + with this NLM */ + + NX_WRAP_INTERFACE(CommandLineInterpreter, 2, (void*)&(ConsoleHandler.parser)); + + ConsoleHandler.rTag = AllocateResourceTag(getnlmhandle(), "Command Line Processor", + ConsoleCommandSignature); + if (!ConsoleHandler.rTag) + { + printf("Error on allocate resource tag\n"); + return 1; + } + + RegisterConsoleCommand(&ConsoleHandler); + + /* The Remove procedure unregisters the console handler */ + + return 0; +} + +static void RemoveConsoleHandler(void) +{ + UnRegisterConsoleCommand(&ConsoleHandler); + NX_UNWRAP_INTERFACE(ConsoleHandler.parser); +} + +static const char *set_threads_to_start(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_free_threads(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_min_free = atoi(arg); + return NULL; +} + +static const char *set_max_free_threads(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_max_free = atoi(arg); + return NULL; +} + +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_limit = atoi(arg); + return NULL; +} + +static const command_rec netware_mpm_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("StartThreads", set_threads_to_start, NULL, RSRC_CONF, + "Number of worker threads launched at server startup"), +AP_INIT_TAKE1("MinSpareThreads", set_min_free_threads, NULL, RSRC_CONF, + "Minimum number of idle threads, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareThreads", set_max_free_threads, NULL, RSRC_CONF, + "Maximum number of idle threads"), +AP_INIT_TAKE1("MaxThreads", set_thread_limit, NULL, RSRC_CONF, + "Maximum number of worker threads alive at the same time"), +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_netware) = { + MPM20_MODULE_STUFF, + netware_rewrite_args, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + netware_mpm_cmds, /* command apr_table_t */ + netware_mpm_hooks, /* register hooks */ +}; diff --git a/server/mpm/prefork/Makefile.in b/server/mpm/prefork/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/prefork/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/prefork/config.m4 b/server/mpm/prefork/config.m4 new file mode 100644 index 0000000..296f834 --- /dev/null +++ b/server/mpm/prefork/config.m4 @@ -0,0 +1,7 @@ +AC_MSG_CHECKING(if prefork MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(prefork, yes, no) +fi diff --git a/server/mpm/prefork/config3.m4 b/server/mpm/prefork/config3.m4 new file mode 100644 index 0000000..25fd8df --- /dev/null +++ b/server/mpm/prefork/config3.m4 @@ -0,0 +1 @@ +APACHE_MPM_MODULE(prefork, $enable_mpm_prefork) diff --git a/server/mpm/prefork/mpm_default.h b/server/mpm/prefork/mpm_default.h new file mode 100644 index 0000000..55b038b --- /dev/null +++ b/server/mpm/prefork/mpm_default.h @@ -0,0 +1,51 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file prefork/mpm_default.h + * @brief Prefork MPM defaults + * + * @defgroup APACHE_MPM_PREFORK Prefork MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 5 +#endif + +/* Maximum number of *free* server processes --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_DAEMON +#define DEFAULT_MAX_FREE_DAEMON 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_DAEMON +#define DEFAULT_MIN_FREE_DAEMON 5 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c new file mode 100644 index 0000000..b5adb57 --- /dev/null +++ b/server/mpm/prefork/prefork.c @@ -0,0 +1,1563 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "scoreboard.h" +#include "ap_mpm.h" +#include "util_mutex.h" +#include "unixd.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "ap_mmn.h" +#include "apr_poll.h" + +#include <stdlib.h> + +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_SYS_PROCESSOR_H +#include <sys/processor.h> /* for bindprocessor() */ +#endif + +#include <signal.h> +#include <sys/times.h> + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_SERVER_LIMIT +#define DEFAULT_SERVER_LIMIT 256 +#endif + +/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_SERVER_LIMIT +#define MAX_SERVER_LIMIT 200000 +#endif + +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 1 +#endif + +/* config globals */ + +static int ap_daemons_to_start=0; +static int ap_daemons_min_free=0; +static int ap_daemons_max_free=0; +static int ap_daemons_limit=0; /* MaxRequestWorkers */ +static int server_limit = 0; + +/* data retained by prefork across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook + */ +typedef struct prefork_retained_data { + ap_unixd_mpm_retained_data *mpm; + + int first_server_limit; + int maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers 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; +} prefork_retained_data; +static prefork_retained_data *retained; + +typedef struct prefork_child_bucket { + ap_pod_t *pod; + ap_listen_rec *listeners; + apr_proc_mutex_t *mutex; +} prefork_child_bucket; +static prefork_child_bucket *all_buckets, /* All listeners buckets */ + *my_bucket; /* Current child bucket */ + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) + +/* one_process --- debugging mode variable; can be set from the command line + * with the -X flag. If set, this gets you the child_main loop running + * in the process which originally started up (no detach, no make_child), + * which is a pretty nice debugging environment. (You'll get a SIGHUP + * early in standalone_main; just continue through. This is the server + * trying to kill off any child processes which it might have lying + * around --- Apache doesn't keep track of their pids, it just sends + * SIGHUP to the process group, ignoring it in the root process. + * Continue through and you'll be fine.). + */ + +static int one_process = 0; + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pchild; /* Pool for httpd child stuff */ + +static pid_t ap_my_pid; /* it seems silly to call getpid all the time */ +static pid_t parent_pid; +static int my_child_num; + +#ifdef GPROF +/* + * change directory for gprof to plop the gmon.out file + * configure in httpd.conf: + * GprofDir $RuntimeDir/ -> $ServerRoot/$RuntimeDir/gmon.out + * GprofDir $RuntimeDir/% -> $ServerRoot/$RuntimeDir/gprof.$pid/gmon.out + */ +static void chdir_for_gprof(void) +{ + core_server_config *sconf = + ap_get_core_module_config(ap_server_conf->module_config); + char *dir = sconf->gprof_dir; + const char *use_dir; + + if(dir) { + apr_status_t res; + char *buf = NULL ; + int len = strlen(sconf->gprof_dir) - 1; + if(*(dir + len) == '%') { + dir[len] = '\0'; + buf = ap_append_pid(pconf, dir, "gprof."); + } + use_dir = ap_server_root_relative(pconf, buf ? buf : dir); + res = apr_dir_make(use_dir, + APR_UREAD | APR_UWRITE | APR_UEXECUTE | + APR_GREAD | APR_GEXECUTE | + APR_WREAD | APR_WEXECUTE, pconf); + if(res != APR_SUCCESS && !APR_STATUS_IS_EEXIST(res)) { + ap_log_error(APLOG_MARK, APLOG_ERR, res, ap_server_conf, APLOGNO(00142) + "gprof: error creating directory %s", dir); + } + } + else { + use_dir = ap_runtime_dir_relative(pconf, ""); + } + + chdir(use_dir); +} +#else +#define chdir_for_gprof() +#endif + +static void prefork_note_child_killed(int childnum, pid_t pid, + ap_generation_t gen) +{ + AP_DEBUG_ASSERT(childnum != -1); /* no scoreboard squatting with this MPM */ + 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; +} + +static void prefork_note_child_started(int slot, pid_t pid) +{ + ap_generation_t gen = retained->mpm->my_generation; + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = gen; + ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); +} + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + apr_signal(SIGHUP, SIG_IGN); + apr_signal(SIGTERM, SIG_IGN); + + if (code == 0) { + ap_run_child_stopping(pchild, 0); + } + + if (pchild) { + apr_pool_destroy(pchild); + } + + if (one_process) { + prefork_note_child_killed(/* slot */ 0, 0, 0); + } + + ap_mpm_pod_close(my_bucket->pod); + chdir_for_gprof(); + exit(code); +} + +static apr_status_t accept_mutex_on(void) +{ + apr_status_t rv = apr_proc_mutex_lock(my_bucket->mutex); + if (rv != APR_SUCCESS) { + const char *msg = "couldn't grab the accept mutex"; + + if (retained->mpm->my_generation != + ap_scoreboard_image->global->running_generation) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00143) "%s", msg); + clean_child_exit(0); + } + else { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00144) "%s", msg); + exit(APEXIT_CHILDFATAL); + } + } + return APR_SUCCESS; +} + +static apr_status_t accept_mutex_off(void) +{ + apr_status_t rv = apr_proc_mutex_unlock(my_bucket->mutex); + if (rv != APR_SUCCESS) { + const char *msg = "couldn't release the accept mutex"; + + if (retained->mpm->my_generation != + ap_scoreboard_image->global->running_generation) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00145) "%s", msg); + /* don't exit here... we have a connection to + * process, after which point we'll see that the + * generation changed and we'll exit cleanly + */ + } + else { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00146) "%s", msg); + exit(APEXIT_CHILDFATAL); + } + } + return APR_SUCCESS; +} + +/* On some architectures it's safe to do unserialized accept()s in the single + * Listen case. But it's never safe to do it in the case where there's + * multiple Listen statements. Define SINGLE_LISTEN_UNSERIALIZED_ACCEPT + * when it's safe in the single Listen case. + */ +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT +#define SAFE_ACCEPT(stmt) (ap_listeners->next ? (stmt) : APR_SUCCESS) +#else +#define SAFE_ACCEPT(stmt) (stmt) +#endif + +static int prefork_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch(query_code){ + case AP_MPMQ_MAX_DAEMON_USED: + *result = ap_daemons_limit; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = server_limit; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = HARD_THREAD_LIMIT; + break; + case AP_MPMQ_MAX_THREADS: + *result = 1; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = ap_daemons_min_free; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = ap_daemons_max_free; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = ap_daemons_limit; + break; + case AP_MPMQ_MPM_STATE: + *result = retained->mpm->mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = retained->mpm->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static const char *prefork_get_name(void) +{ + return "prefork"; +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static void just_die(int sig) +{ + clean_child_exit(0); +} + +/* volatile because it's updated from a signal handler */ +static int volatile die_now = 0; + +static void stop_listening(int sig) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + ap_close_listeners_ex(my_bucket->listeners); + + /* For a graceful stop, we want the child to exit when done */ + die_now = 1; +} + +/***************************************************************** + * Child process main loop. + * The following vars are static to avoid getting clobbered by longjmp(); + * they are really private to child_main. + */ + +static int requests_this_child; +static int num_listensocks = 0; + +#if APR_HAS_THREADS +static void child_sigmask(sigset_t *new_mask, sigset_t *old_mask) +{ +#if defined(SIGPROCMASK_SETS_THREAD_MASK) + sigprocmask(SIG_SETMASK, new_mask, old_mask); +#else + pthread_sigmask(SIG_SETMASK, new_mask, old_mask); +#endif +} +#endif + +static void child_main(int child_num_arg, int child_bucket) +{ +#if APR_HAS_THREADS + apr_thread_t *thd = NULL; + sigset_t sig_mask; +#endif + apr_pool_t *ptrans; + apr_allocator_t *allocator; + apr_status_t status; + int i; + ap_listen_rec *lr; + apr_pollset_t *pollset; + ap_sb_handle_t *sbh; + apr_bucket_alloc_t *bucket_alloc; + int last_poll_idx = 0; + const char *lockfile; + + /* for benefit of any hooks that run as this child initializes */ + retained->mpm->mpm_state = AP_MPMQ_STARTING; + + my_child_num = child_num_arg; + ap_my_pid = getpid(); + requests_this_child = 0; + + ap_fatal_signal_child_setup(ap_server_conf); + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + apr_pool_create_ex(&pchild, pconf, NULL, allocator); + apr_allocator_owner_set(allocator, pchild); + apr_pool_tag(pchild, "pchild"); + +#if AP_HAS_THREAD_LOCAL + if (one_process) { + thd = ap_thread_current(); + } + else if ((status = ap_thread_main_create(&thd, pchild))) { + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(10378) + "Couldn't initialize child main thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } +#elif APR_HAS_THREADS + { + apr_os_thread_t osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); + } +#endif +#if APR_HAS_THREADS + ap_assert(thd != NULL); +#endif + + apr_pool_create(&ptrans, pchild); + apr_pool_tag(ptrans, "transaction"); + + /* close unused listeners and pods */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (i != child_bucket) { + ap_close_listeners_ex(all_buckets[i].listeners); + ap_mpm_pod_close(all_buckets[i].pod); + } + } + + /* needs to be done before we switch UIDs so we have permissions */ + ap_reopen_scoreboard(pchild, NULL, 0); + status = SAFE_ACCEPT(apr_proc_mutex_child_init(&my_bucket->mutex, + apr_proc_mutex_lockfile(my_bucket->mutex), + pchild)); + if (status != APR_SUCCESS) { + lockfile = apr_proc_mutex_lockfile(my_bucket->mutex); + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00155) + "Couldn't initialize cross-process lock in child " + "(%s) (%s)", + lockfile ? lockfile : "none", + apr_proc_mutex_name(my_bucket->mutex)); + clean_child_exit(APEXIT_CHILDFATAL); + } + + if (ap_run_drop_privileges(pchild, ap_server_conf)) { + clean_child_exit(APEXIT_CHILDFATAL); + } + +#if APR_HAS_THREADS + /* Save the signal mask and block all the signals from being received by + * threads potentially created in child_init() hooks (e.g. mod_watchdog). + */ + child_sigmask(NULL, &sig_mask); + { + apr_status_t rv; + rv = apr_setup_signal_thread(); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10271) + "Couldn't initialize signal thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +#endif /* APR_HAS_THREADS */ + + ap_run_child_init(pchild, ap_server_conf); + +#if APR_HAS_THREADS + /* Restore the original signal mask for this main thread, the only one + * that should possibly get interrupted by signals. + */ + child_sigmask(&sig_mask, NULL); +#endif + + ap_create_sb_handle(&sbh, pchild, my_child_num, 0); + + (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL); + + /* Set up the pollfd array */ + status = apr_pollset_create(&pollset, num_listensocks, pchild, + APR_POLLSET_NOCOPY); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00156) + "Couldn't create pollset in child; check system or user limits"); + clean_child_exit(APEXIT_CHILDSICK); /* assume temporary resource issue */ + } + + for (lr = my_bucket->listeners, i = num_listensocks; i--; lr = lr->next) { + apr_pollfd_t *pfd = apr_pcalloc(pchild, sizeof *pfd); + + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = lr->sd; + pfd->reqevents = APR_POLLIN; + pfd->client_data = lr; + + status = apr_pollset_add(pollset, pfd); + if (status != APR_SUCCESS) { + /* If the child processed a SIGWINCH before setting up the + * pollset, this error path is expected and harmless, + * since the listener fd was already closed; so don't + * pollute the logs in that case. */ + if (!die_now) { + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00157) + "Couldn't add listener to pollset; check system or user limits"); + clean_child_exit(APEXIT_CHILDSICK); + } + clean_child_exit(0); + } + + lr->accept_func = ap_unixd_accept; + } + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + bucket_alloc = apr_bucket_alloc_create(pchild); + + /* die_now is set when AP_SIG_GRACEFUL is received in the child; + * {shutdown,restart}_pending are set when a signal is received while + * running in single process mode. + */ + while (!die_now + && !retained->mpm->shutdown_pending + && !retained->mpm->restart_pending) { + conn_rec *current_conn; + void *csd; + + /* + * (Re)initialize this child to a pre-connection state. + */ + + apr_pool_clear(ptrans); + + if ((ap_max_requests_per_child > 0 + && requests_this_child++ >= ap_max_requests_per_child)) { + clean_child_exit(0); + } + + (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL); + + /* + * Wait for an acceptable connection to arrive. + */ + + /* Lock around "accept", if necessary */ + SAFE_ACCEPT(accept_mutex_on()); + + if (num_listensocks == 1) { + /* There is only one listener record, so refer to that one. */ + lr = my_bucket->listeners; + } + else { + /* multiple listening sockets - need to poll */ + for (;;) { + apr_int32_t numdesc; + const apr_pollfd_t *pdesc; + + /* check for termination first so we don't sleep for a while in + * poll if already signalled + */ + if (die_now /* in graceful stop/restart */ + || retained->mpm->shutdown_pending + || retained->mpm->restart_pending) { + SAFE_ACCEPT(accept_mutex_off()); + clean_child_exit(0); + } + + /* timeout == 10 seconds to avoid a hang at graceful restart/stop + * caused by the closing of sockets by the signal handler + */ + status = apr_pollset_poll(pollset, apr_time_from_sec(10), + &numdesc, &pdesc); + if (status != APR_SUCCESS) { + if (APR_STATUS_IS_TIMEUP(status) || + APR_STATUS_IS_EINTR(status)) { + continue; + } + /* Single Unix documents select as returning errnos + * EBADF, EINTR, and EINVAL... and in none of those + * cases does it make sense to continue. In fact + * on Linux 2.0.x we seem to end up with EFAULT + * occasionally, and we'd loop forever due to it. + */ + ap_log_error(APLOG_MARK, APLOG_ERR, status, + ap_server_conf, APLOGNO(00158) "apr_pollset_poll: (listen)"); + SAFE_ACCEPT(accept_mutex_off()); + clean_child_exit(APEXIT_CHILDSICK); + } + + /* We can always use pdesc[0], but sockets at position N + * could end up completely starved of attention in a very + * busy server. Therefore, we round-robin across the + * returned set of descriptors. While it is possible that + * the returned set of descriptors might flip around and + * continue to starve some sockets, we happen to know the + * internal pollset implementation retains ordering + * stability of the sockets. Thus, the round-robin should + * ensure that a socket will eventually be serviced. + */ + if (last_poll_idx >= numdesc) + last_poll_idx = 0; + + /* Grab a listener record from the client_data of the poll + * descriptor, and advance our saved index to round-robin + * the next fetch. + * + * ### hmm... this descriptor might have POLLERR rather + * ### than POLLIN + */ + lr = pdesc[last_poll_idx++].client_data; + goto got_fd; + } + } + got_fd: + /* if we accept() something we don't want to die, so we have to + * defer the exit + */ + status = lr->accept_func(&csd, lr, ptrans); + + SAFE_ACCEPT(accept_mutex_off()); /* unlock after "accept" */ + + if (status == APR_EGENERAL) { + /* resource shortage or should-not-occur occurred */ + clean_child_exit(APEXIT_CHILDSICK); + } + else if (status != APR_SUCCESS) { + continue; + } + + /* + * We now have a connection, so set it up with the appropriate + * socket options, file descriptors, and read/write buffers. + */ + + current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, my_child_num, sbh, bucket_alloc); + if (current_conn) { +#if APR_HAS_THREADS + current_conn->current_thread = thd; +#endif + ap_process_connection(current_conn, csd); + ap_lingering_close(current_conn); + } + + /* Check the pod and the generation number after processing a + * connection so that we'll go away if a graceful restart occurred + * while we were processing the connection or we are the lucky + * idle server process that gets to die. + */ + if (ap_mpm_pod_check(my_bucket->pod) == APR_SUCCESS) { /* selected as idle? */ + die_now = 1; + } + else if (retained->mpm->my_generation != + ap_scoreboard_image->global->running_generation) { /* restart? */ + /* yeah, this could be non-graceful restart, in which case the + * parent will kill us soon enough, but why bother checking? + */ + die_now = 1; + } + } + apr_pool_clear(ptrans); /* kludge to avoid crash in APR reslist cleanup code */ + clean_child_exit(0); +} + + +static int make_child(server_rec *s, int slot) +{ + int bucket = slot % retained->mpm->num_buckets; + int pid; + + if (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; + } + + if (one_process) { + my_bucket = &all_buckets[0]; + + prefork_note_child_started(slot, getpid()); + child_main(slot, 0); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + (void) ap_update_child_status_from_indexes(slot, 0, SERVER_STARTING, + (request_rec *) NULL); + +#ifdef _OSD_POSIX + /* BS2000 requires a "special" version of fork() before a setuid() call */ + if ((pid = os_fork(ap_unixd_config.user_name)) == -1) { +#else + if ((pid = fork()) == -1) { +#endif + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00159) "fork: Unable to fork new process"); + + /* fork didn't succeed. Fix the scoreboard or else + * it will say SERVER_STARTING forever and ever + */ + (void) ap_update_child_status_from_indexes(slot, 0, SERVER_DEAD, + (request_rec *) NULL); + + /* In case system resources are maxxed out, we don't want + * Apache running away with the CPU trying to fork over and + * over and over again. + */ + sleep(10); + + return -1; + } + + if (!pid) { +#if AP_HAS_THREAD_LOCAL + ap_thread_current_after_fork(); +#endif + + my_bucket = &all_buckets[bucket]; + +#ifdef HAVE_BINDPROCESSOR + /* by default AIX binds to a single processor + * this bit unbinds children which will then bind to another cpu + */ + int status = bindprocessor(BINDPROCESS, (int)getpid(), + PROCESSOR_CLASS_ANY); + if (status != OK) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00160) "processor unbind failed"); + } +#endif + RAISE_SIGSTOP(MAKE_CHILD); + AP_MONCONTROL(1); + /* Disable the parent's signal handlers and set up proper handling in + * the child. + */ + apr_signal(SIGHUP, just_die); + apr_signal(SIGTERM, just_die); + /* Ignore SIGINT in child. This fixes race-conditions in signals + * handling when httpd is running on foreground and user hits ctrl+c. + * In this case, SIGINT is sent to all children followed by SIGTERM + * from the main process, which interrupts the SIGINT handler and + * leads to inconsistency. + */ + apr_signal(SIGINT, SIG_IGN); + /* The child process just closes listeners on AP_SIG_GRACEFUL. + * The pod is used for signalling the graceful restart. + */ + apr_signal(AP_SIG_GRACEFUL, stop_listening); + child_main(slot, bucket); + } + + prefork_note_child_started(slot, pid); + + return 0; +} + + +/* start up a bunch of children */ +static void startup_children(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < ap_daemons_limit; ++i) { + if (ap_scoreboard_image->servers[i][0].status != SERVER_DEAD) { + continue; + } + if (make_child(ap_server_conf, i) < 0) { + break; + } + --number_to_start; + } +} + +static void perform_idle_server_maintenance(apr_pool_t *p) +{ + int i; + int idle_count; + worker_score *ws; + int free_length; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead; + int total_non_dead; + + /* initialize the free_list */ + free_length = 0; + + idle_count = 0; + last_non_dead = -1; + total_non_dead = 0; + + for (i = 0; i < ap_daemons_limit; ++i) { + int status; + + if (i >= retained->max_daemons_limit && free_length == retained->idle_spawn_rate) + break; + ws = &ap_scoreboard_image->servers[i][0]; + status = ws->status; + if (status == SERVER_DEAD) { + /* try to keep children numbers as low as possible */ + if (free_length < retained->idle_spawn_rate) { + free_slots[free_length] = i; + ++free_length; + } + } + else { + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (status <= SERVER_READY) { + ++ idle_count; + } + + ++total_non_dead; + last_non_dead = i; + } + } + retained->max_daemons_limit = last_non_dead + 1; + if (idle_count > ap_daemons_max_free) { + static int bucket_kill_child_record = -1; + /* kill off one child... we use the pod because that'll cause it to + * shut down gracefully, in case it happened to pick up a request + * while we were counting + */ + bucket_kill_child_record = (bucket_kill_child_record + 1) % retained->mpm->num_buckets; + ap_mpm_pod_signal(all_buckets[bucket_kill_child_record].pod); + retained->idle_spawn_rate = 1; + } + else if (idle_count < ap_daemons_min_free) { + /* terminate the free list */ + if (free_length == 0) { + /* only report this condition once */ + if (!retained->maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00161) + "server reached MaxRequestWorkers setting, consider" + " raising the MaxRequestWorkers setting"); + retained->maxclients_reported = 1; + } + retained->idle_spawn_rate = 1; + } + else { + if (retained->idle_spawn_rate >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00162) + "server seems busy, (you may need " + "to increase StartServers, or Min/MaxSpareServers), " + "spawning %d children, there are %d idle, and " + "%d total children", retained->idle_spawn_rate, + idle_count, total_non_dead); + } + for (i = 0; i < free_length; ++i) { + make_child(ap_server_conf, free_slots[i]); + } + /* 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 (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; + } + else if (retained->idle_spawn_rate < MAX_SPAWN_RATE) { + retained->idle_spawn_rate *= 2; + } + } + } + else { + retained->idle_spawn_rate = 1; + } +} + +/***************************************************************** + * Executive routines. + */ + +static int prefork_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +{ + int index; + int remaining_children_to_start; + int i; + + ap_log_pid(pconf, ap_pid_fname); + + if (!retained->mpm->was_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + /* fix the generation number in the global score; we just got a new, + * cleared scoreboard + */ + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + } + + ap_unixd_mpm_set_signals(pconf, one_process); + + if (one_process) { + AP_MONCONTROL(1); + make_child(ap_server_conf, 0); + /* NOTREACHED */ + ap_assert(0); + return !OK; + } + + /* Don't thrash since num_buckets depends on the + * system and the number of online CPU cores... + */ + if (ap_daemons_limit < retained->mpm->num_buckets) + ap_daemons_limit = retained->mpm->num_buckets; + if (ap_daemons_to_start < retained->mpm->num_buckets) + ap_daemons_to_start = retained->mpm->num_buckets; + if (ap_daemons_min_free < retained->mpm->num_buckets) + ap_daemons_min_free = retained->mpm->num_buckets; + if (ap_daemons_max_free < ap_daemons_min_free + retained->mpm->num_buckets) + ap_daemons_max_free = ap_daemons_min_free + retained->mpm->num_buckets; + + /* If we're doing a graceful_restart then we're going to see a lot + * of children exiting immediately when we get into the main loop + * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty + * rapidly... and for each one that exits we'll start a new one until + * we reach at least daemons_min_free. But we may be permitted to + * start more than that, so we'll just keep track of how many we're + * supposed to start up without the 1 second penalty between each fork. + */ + remaining_children_to_start = ap_daemons_to_start; + if (remaining_children_to_start > ap_daemons_limit) { + remaining_children_to_start = ap_daemons_limit; + } + if (!retained->mpm->was_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 + */ + retained->hold_off_on_exponential_spawning = 10; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00163) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00164) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00165) + "Accept mutex: %s (default: %s)", + (all_buckets[0].mutex) + ? apr_proc_mutex_name(all_buckets[0].mutex) + : "none", + apr_proc_mutex_defname()); + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + while (!retained->mpm->restart_pending && !retained->mpm->shutdown_pending) { + int child_slot; + apr_exit_why_e exitwhy; + int status, processed_status; + /* this is a memory leak, but I'll fix it later. */ + apr_proc_t pid; + + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); + + /* XXX: if it takes longer than 1 second for all our children + * to start up and get into IDLE state then we may spawn an + * extra child + */ + if (pid.pid != -1) { + processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); + if (processed_status == APEXIT_CHILDFATAL) { + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->mpm->my_generation) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00166) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + } + } + + /* non-fatal death... note that it's gone in the scoreboard. */ + if (child_slot >= 0) { + (void) ap_update_child_status_from_indexes(child_slot, 0, SERVER_DEAD, + (request_rec *) NULL); + prefork_note_child_killed(child_slot, 0, 0); + if (processed_status == APEXIT_CHILDSICK) { + /* child detected a resource shortage (E[NM]FILE, ENOBUFS, etc) + * cut the fork rate to the minimum + */ + retained->idle_spawn_rate = 1; + } + else if (remaining_children_to_start + && child_slot < ap_daemons_limit) { + /* we're still doing a 1-for-1 replacement of dead + * children with new children + */ + make_child(ap_server_conf, child_slot); + --remaining_children_to_start; + } +#if APR_HAS_OTHER_CHILD + } + else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, status) == APR_SUCCESS) { + /* handled */ +#endif + } + else if (retained->mpm->was_graceful) { + /* Great, we've probably just lost a slot in the + * scoreboard. Somehow we don't know about this + * child. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + 0, ap_server_conf, APLOGNO(00167) + "long lost child came home! (pid %ld)", (long)pid.pid); + } + /* Don't perform idle maintenance when a child dies, + * only do it when there's a timeout. Remember only a + * finite number of children can die, and it's pretty + * pathological for a lot to die suddenly. + */ + continue; + } + else if (remaining_children_to_start) { + /* we hit a 1 second timeout in which none of the previous + * generation of children needed to be reaped... so assume + * they're all done, and pick up the slack if any is left. + */ + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + /* In any event we really shouldn't do the code below because + * few of the servers we just started are in the IDLE state + * yet, so we'd mistakenly create an extra server. + */ + continue; + } + + perform_idle_server_maintenance(pconf); + } + + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { + /* Time to shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + if (ap_unixd_killpg(getpgrp(), SIGTERM) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00168) "killpg SIGTERM"); + } + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + prefork_note_child_killed); + + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00169) + "caught SIGTERM, shutting down"); + + return DONE; + } + + if (retained->mpm->shutdown_pending) { + /* Time to perform a graceful shut down: + * Reap the inactive children, and ask the active ones + * to close their listeners, then wait until they are + * all done to exit. + */ + int active_children; + apr_time_t cutoff = 0; + + /* Stop listening */ + ap_close_listeners(); + + /* kill off the idle ones */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + ap_mpm_pod_killpg(all_buckets[i].pod, retained->max_daemons_limit); + } + + /* Send SIGUSR1 to the active children */ + active_children = 0; + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) { + /* Ask each child to close its listeners. */ + ap_mpm_safe_kill(MPM_CHILD_PID(index), AP_SIG_GRACEFUL); + active_children++; + } + } + + /* Allow each child which actually finished to exit */ + ap_relieve_child_processes(prefork_note_child_killed); + + /* cleanup pid file */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00170) + "caught " AP_SIG_GRACEFUL_STOP_STRING ", shutting down gracefully"); + + if (ap_graceful_shutdown_timeout) { + cutoff = apr_time_now() + + apr_time_from_sec(ap_graceful_shutdown_timeout); + } + + /* Don't really exit until each child has finished */ + retained->mpm->shutdown_pending = 0; + do { + /* Pause for a second */ + sleep(1); + + /* Relieve any children which have now exited */ + ap_relieve_child_processes(prefork_note_child_killed); + + active_children = 0; + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) { + active_children = 1; + /* Having just one child is enough to stay around */ + break; + } + } + } while (!retained->mpm->shutdown_pending && active_children && + (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff)); + + /* We might be here because we received SIGTERM, either + * way, try and make sure that all of our processes are + * really dead. + */ + ap_unixd_killpg(getpgrp(), SIGTERM); + + return DONE; + } + + /* we've been told to restart */ + if (one_process) { + /* not worth thinking about */ + return DONE; + } + + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++retained->mpm->my_generation; + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + + if (!retained->mpm->is_ungraceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00171) + "Graceful restart requested, doing restart"); + + /* kill off the idle ones */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + ap_mpm_pod_killpg(all_buckets[i].pod, retained->max_daemons_limit); + } + + /* This is mostly for debugging... so that we know what is still + * gracefully dealing with existing request. This will break + * in a very nasty way if we ever have the scoreboard totally + * file-based (no shared memory) + */ + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) { + ap_scoreboard_image->servers[index][0].status = SERVER_GRACEFUL; + /* Ask each child to close its listeners. + * + * NOTE: we use the scoreboard, because if we send SIGUSR1 + * to every process in the group, this may include CGI's, + * piped loggers, etc. They almost certainly won't handle + * it gracefully. + */ + ap_mpm_safe_kill(ap_scoreboard_image->parent[index].pid, AP_SIG_GRACEFUL); + } + } + } + else { + /* Kill 'em off */ + if (ap_unixd_killpg(getpgrp(), SIGHUP) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00172) "killpg SIGHUP"); + } + ap_reclaim_child_processes(0, /* Not when just starting up */ + prefork_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00173) + "SIGHUP received. Attempting to restart"); + } + + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int prefork_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + int level_flags = 0; + ap_listen_rec **listen_buckets; + apr_status_t rv; + char id[16]; + int i; + + pconf = p; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } + + if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "no listening sockets available, shutting down"); + return !OK; + } + + if (one_process) { + retained->mpm->num_buckets = 1; + } + else if (!retained->mpm->was_graceful) { + /* Preserve the number of buckets on graceful restarts. */ + retained->mpm->num_buckets = 0; + } + if ((rv = ap_duplicate_listeners(pconf, ap_server_conf, + &listen_buckets, &retained->mpm->num_buckets))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not duplicate listeners"); + return !OK; + } + all_buckets = apr_pcalloc(pconf, retained->mpm->num_buckets * + sizeof(prefork_child_bucket)); + for (i = 0; i < retained->mpm->num_buckets; i++) { + if ((rv = ap_mpm_pod_open(pconf, &all_buckets[i].pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); + return !OK; + } + /* Initialize cross-process accept lock (safe accept needed only) */ + if ((rv = SAFE_ACCEPT((apr_snprintf(id, sizeof id, "%i", i), + ap_proc_mutex_create(&all_buckets[i].mutex, + NULL, AP_ACCEPT_MUTEX_TYPE, + id, s, pconf, 0))))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not create accept mutex"); + return !OK; + } + all_buckets[i].listeners = listen_buckets[i]; + } + + return OK; +} + +static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) +{ + int no_detach, debug, foreground; + apr_status_t rv; + const char *userdata_key = "mpm_prefork_module"; + + debug = ap_exists_config_define("DEBUG"); + + if (debug) { + foreground = one_process = 1; + no_detach = 0; + } + else + { + no_detach = ap_exists_config_define("NO_DETACH"); + one_process = ap_exists_config_define("ONE_PROCESS"); + foreground = ap_exists_config_define("FOREGROUND"); + } + + ap_mutex_register(p, AP_ACCEPT_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0); + + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->mpm = ap_unixd_mpm_get_retained_data(); + retained->idle_spawn_rate = 1; + } + retained->mpm->mpm_state = AP_MPMQ_STARTING; + if (retained->mpm->baton != retained) { + retained->mpm->was_graceful = 0; + retained->mpm->baton = retained; + } + ++retained->mpm->module_loads; + + /* sigh, want this only the second time around */ + if (retained->mpm->module_loads == 2) { + if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, p /* == pconf */); + rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND + : APR_PROC_DETACH_DAEMONIZE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00174) + "apr_proc_detach failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + parent_pid = ap_my_pid = getpid(); + + ap_listen_pre_config(); + ap_daemons_to_start = DEFAULT_START_DAEMON; + ap_daemons_min_free = DEFAULT_MIN_FREE_DAEMON; + ap_daemons_max_free = DEFAULT_MAX_FREE_DAEMON; + server_limit = DEFAULT_SERVER_LIMIT; + ap_daemons_limit = server_limit; + ap_extended_status = 0; + + return OK; +} + +static int prefork_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00175) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of %d servers, decreasing to %d.", + server_limit, MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00176) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00177) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00178) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00179) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (ap_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00180) + "WARNING: MaxRequestWorkers of %d exceeds ServerLimit " + "value of %d servers, decreasing MaxRequestWorkers to %d. " + "To increase, please see the ServerLimit directive.", + ap_daemons_limit, server_limit, server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00181) + "MaxRequestWorkers of %d exceeds ServerLimit value " + "of %d, decreasing to match", + ap_daemons_limit, server_limit); + } + ap_daemons_limit = server_limit; + } + else if (ap_daemons_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00182) + "WARNING: MaxRequestWorkers of %d not allowed, " + "increasing to 1.", ap_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00183) + "MaxRequestWorkers of %d not allowed, increasing to 1", + ap_daemons_limit); + } + ap_daemons_limit = 1; + } + + /* ap_daemons_to_start > ap_daemons_limit checked in prefork_run() */ + if (ap_daemons_to_start < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00184) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00185) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (ap_daemons_min_free < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00186) + "WARNING: MinSpareServers of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", ap_daemons_min_free); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00187) + "MinSpareServers of %d not allowed, increasing to 1", + ap_daemons_min_free); + } + ap_daemons_min_free = 1; + } + + /* ap_daemons_max_free < ap_daemons_min_free + 1 checked in prefork_run() */ + + return OK; +} + +static void prefork_hooks(apr_pool_t *p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + + ap_hook_open_logs(prefork_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + /* we need to set the MPM state before other pre-config hooks use MPM query + * to retrieve it, so register as REALLY_FIRST + */ + ap_hook_pre_config(prefork_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_check_config(prefork_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(prefork_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(prefork_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(prefork_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_free_servers(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_min_free = atoi(arg); + return NULL; +} + +static const char *set_max_free_servers(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_max_free = atoi(arg); + return NULL; +} + +static const char *set_max_clients (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00188) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); + } + ap_daemons_limit = atoi(arg); + return NULL; +} + +static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + server_limit = atoi(arg); + return NULL; +} + +static const command_rec prefork_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup"), +AP_INIT_TAKE1("MinSpareServers", set_min_free_servers, NULL, RSRC_CONF, + "Minimum number of idle children, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareServers", set_max_free_servers, NULL, RSRC_CONF, + "Maximum number of idle children"), +AP_INIT_TAKE1("MaxClients", set_max_clients, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), +AP_INIT_TAKE1("MaxRequestWorkers", set_max_clients, NULL, RSRC_CONF, + "Maximum number of children alive at the same time"), +AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF, + "Maximum value of MaxRequestWorkers for this run of Apache"), +AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_prefork) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + prefork_cmds, /* command apr_table_t */ + prefork_hooks, /* register hooks */ +}; diff --git a/server/mpm/winnt/Makefile.in b/server/mpm/winnt/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/winnt/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c new file mode 100644 index 0000000..05151a8 --- /dev/null +++ b/server/mpm/winnt/child.c @@ -0,0 +1,1306 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WIN32 + +#include "apr.h" +#include <process.h> +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "http_vhost.h" /* for ap_update_vhost_given_ip */ +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" +#include "apr_buckets.h" +#include "scoreboard.h" + +#ifdef __MINGW32__ +#include <mswsock.h> + +#ifndef WSAID_ACCEPTEX +#define WSAID_ACCEPTEX \ + {0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +typedef BOOL (WINAPI *LPFN_ACCEPTEX)(SOCKET, SOCKET, PVOID, DWORD, DWORD, DWORD, LPDWORD, LPOVERLAPPED); +#endif /* WSAID_ACCEPTEX */ + +#ifndef WSAID_GETACCEPTEXSOCKADDRS +#define WSAID_GETACCEPTEXSOCKADDRS \ + {0xb5367df2, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +typedef VOID (WINAPI *LPFN_GETACCEPTEXSOCKADDRS)(PVOID, DWORD, DWORD, DWORD, + struct sockaddr **, LPINT, + struct sockaddr **, LPINT); +#endif /* WSAID_GETACCEPTEXSOCKADDRS */ + +#endif /* __MINGW32__ */ + +/* + * The Windows MPM uses a queue of completion contexts that it passes + * between the accept threads and the worker threads. Declare the + * functions to access the queue and the structures passed on the + * queue in the header file to enable modules to access them + * if necessary. The queue resides in the MPM. + */ +#ifdef CONTAINING_RECORD +#undef CONTAINING_RECORD +#endif +#define CONTAINING_RECORD(address, type, field) ((type *)( \ + (char *)(address) - \ + (char *)(&((type *)0)->field))) +#if APR_HAVE_IPV6 +#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN6)+16) +#else +#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN)+16) +#endif + +APLOG_USE_MODULE(mpm_winnt); + +/* Queue for managing the passing of winnt_conn_ctx_t between + * the accept and worker threads. + */ +typedef struct winnt_conn_ctx_t_s { + struct winnt_conn_ctx_t_s *next; + OVERLAPPED overlapped; + apr_socket_t *sock; + SOCKET accept_socket; + char buff[2*PADDED_ADDR_SIZE]; + struct sockaddr *sa_server; + int sa_server_len; + struct sockaddr *sa_client; + int sa_client_len; + apr_pool_t *ptrans; + apr_bucket_alloc_t *ba; + apr_bucket *data; +#if APR_HAVE_IPV6 + short socket_family; +#endif +} winnt_conn_ctx_t; + +typedef enum { + IOCP_CONNECTION_ACCEPTED = 1, + IOCP_WAIT_FOR_RECEIVE = 2, + IOCP_WAIT_FOR_TRANSMITFILE = 3, + IOCP_SHUTDOWN = 4 +} io_state_e; + +static apr_pool_t *pchild; +static int shutdown_in_progress = 0; +static int workers_may_exit = 0; +static unsigned int g_blocked_threads = 0; +static HANDLE max_requests_per_child_event; + +static apr_thread_mutex_t *child_lock; +static apr_thread_mutex_t *qlock; +static winnt_conn_ctx_t *qhead = NULL; +static winnt_conn_ctx_t *qtail = NULL; +static apr_uint32_t num_completion_contexts = 0; +static apr_uint32_t max_num_completion_contexts = 0; +static HANDLE ThreadDispatchIOCP = NULL; +static HANDLE qwait_event = NULL; + +static void mpm_recycle_completion_context(winnt_conn_ctx_t *context) +{ + /* Recycle the completion context. + * - clear the ptrans pool + * - put the context on the queue to be consumed by the accept thread + * Note: + * context->accept_socket may be in a disconnected but reusable + * state so -don't- close it. + */ + if (context) { + HANDLE saved_event; + + apr_pool_clear(context->ptrans); + context->ba = apr_bucket_alloc_create(context->ptrans); + context->next = NULL; + + saved_event = context->overlapped.hEvent; + memset(&context->overlapped, 0, sizeof(context->overlapped)); + context->overlapped.hEvent = saved_event; + ResetEvent(context->overlapped.hEvent); + + apr_thread_mutex_lock(qlock); + if (qtail) { + qtail->next = context; + } else { + qhead = context; + SetEvent(qwait_event); + } + qtail = context; + apr_thread_mutex_unlock(qlock); + } +} + +static winnt_conn_ctx_t *mpm_get_completion_context(int *timeout) +{ + apr_status_t rv; + winnt_conn_ctx_t *context = NULL; + + *timeout = 0; + while (1) { + /* Grab a context off the queue */ + apr_thread_mutex_lock(qlock); + if (qhead) { + context = qhead; + qhead = qhead->next; + if (!qhead) + qtail = NULL; + } else { + ResetEvent(qwait_event); + } + apr_thread_mutex_unlock(qlock); + + if (!context) { + /* We failed to grab a context off the queue, consider allocating + * a new one out of the child pool. There may be up to + * (ap_threads_per_child + num_listeners) contexts in the system + * at once. + */ + if (num_completion_contexts >= max_num_completion_contexts) { + /* All workers are busy, need to wait for one */ + static int reported = 0; + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00326) + "Server ran out of threads to serve " + "requests. Consider raising the " + "ThreadsPerChild setting"); + reported = 1; + } + + /* Wait for a worker to free a context. Once per second, give + * the caller a chance to check for shutdown. If the wait + * succeeds, get the context off the queue. It must be + * available, since there's only one consumer. + */ + rv = WaitForSingleObject(qwait_event, 1000); + if (rv == WAIT_OBJECT_0) + continue; + else { + if (rv == WAIT_TIMEOUT) { + /* somewhat-normal condition where threads are busy */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00327) + "mpm_get_completion_context: Failed to get a " + "free context within 1 second"); + *timeout = 1; + } + else { + /* should be the unexpected, generic WAIT_FAILED */ + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00328) + "mpm_get_completion_context: " + "WaitForSingleObject failed to get free context"); + } + return NULL; + } + } else { + /* Allocate another context. + * Note: Multiple failures in the next two steps will cause + * the pchild pool to 'leak' storage. I don't think this + * is worth fixing... + */ + apr_allocator_t *allocator; + + apr_thread_mutex_lock(child_lock); + context = (winnt_conn_ctx_t *)apr_pcalloc(pchild, + sizeof(winnt_conn_ctx_t)); + + + context->overlapped.hEvent = CreateEvent(NULL, TRUE, + FALSE, NULL); + if (context->overlapped.hEvent == NULL) { + /* Hopefully this is a temporary condition ... */ + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00329) + "mpm_get_completion_context: " + "CreateEvent failed."); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + + /* Create the transaction pool */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + rv = apr_pool_create_ex(&context->ptrans, pchild, NULL, + allocator); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00330) + "mpm_get_completion_context: Failed " + "to create the transaction pool."); + CloseHandle(context->overlapped.hEvent); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + apr_allocator_owner_set(allocator, context->ptrans); + apr_pool_tag(context->ptrans, "transaction"); + + context->accept_socket = INVALID_SOCKET; + context->ba = apr_bucket_alloc_create(context->ptrans); + apr_atomic_inc32(&num_completion_contexts); + + apr_thread_mutex_unlock(child_lock); + break; + } + } else { + /* Got a context from the queue */ + break; + } + } + + return context; +} + +typedef enum { + ACCEPT_FILTER_NONE = 0, + ACCEPT_FILTER_CONNECT = 1 +} accept_filter_e; + +static const char * accept_filter_to_string(accept_filter_e accf) +{ + switch (accf) { + case ACCEPT_FILTER_NONE: + return "none"; + case ACCEPT_FILTER_CONNECT: + return "connect"; + default: + return ""; + } +} + +static accept_filter_e get_accept_filter(const char *protocol) +{ + core_server_config *core_sconf; + const char *name; + + core_sconf = ap_get_core_module_config(ap_server_conf->module_config); + name = apr_table_get(core_sconf->accf_map, protocol); + if (!name) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + APLOGNO(02531) "winnt_accept: Listen protocol '%s' has " + "no known accept filter. Using 'none' instead", + protocol); + return ACCEPT_FILTER_NONE; + } + else if (strcmp(name, "data") == 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + APLOGNO(03458) "winnt_accept: 'data' accept filter is no " + "longer supported. Using 'connect' instead"); + return ACCEPT_FILTER_CONNECT; + } + else if (strcmp(name, "connect") == 0) { + return ACCEPT_FILTER_CONNECT; + } + else if (strcmp(name, "none") == 0) { + return ACCEPT_FILTER_NONE; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00331) + "winnt_accept: unrecognized AcceptFilter '%s', " + "only 'data', 'connect' or 'none' are valid. " + "Using 'none' instead", name); + return ACCEPT_FILTER_NONE; + } +} + +/* Windows NT/2000 specific code... + * Accept processing for on Windows NT uses a producer/consumer queue + * model. An accept thread accepts connections off the network then issues + * PostQueuedCompletionStatus() to awake a thread blocked on the ThreadDispatch + * IOCompletionPort. + * + * winnt_accept() + * One or more accept threads run in this function, each of which accepts + * connections off the network and calls PostQueuedCompletionStatus() to + * queue an io completion packet to the ThreadDispatch IOCompletionPort. + * winnt_get_connection() + * Worker threads block on the ThreadDispatch IOCompletionPort awaiting + * connections to service. + */ +#define MAX_ACCEPTEX_ERR_COUNT 10 + +static unsigned int __stdcall winnt_accept(void *lr_) +{ + ap_listen_rec *lr = (ap_listen_rec *)lr_; + apr_os_sock_info_t sockinfo; + winnt_conn_ctx_t *context = NULL; + DWORD BytesRead = 0; + SOCKET nlsd; + LPFN_ACCEPTEX lpfnAcceptEx = NULL; + LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = NULL; + GUID GuidAcceptEx = WSAID_ACCEPTEX; + GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; + int rv; + accept_filter_e accf; + int err_count = 0; + HANDLE events[3]; +#if APR_HAVE_IPV6 + SOCKADDR_STORAGE ss_listen; + int namelen = sizeof(ss_listen); +#endif + u_long zero = 0; + + apr_os_sock_get(&nlsd, lr->sd); + +#if APR_HAVE_IPV6 + if (getsockname(nlsd, (struct sockaddr *)&ss_listen, &namelen) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(00332) + "winnt_accept: getsockname error on listening socket, " + "is IPv6 available?"); + return 1; + } +#endif + + accf = get_accept_filter(lr->protocol); + if (accf == ACCEPT_FILTER_CONNECT) + { + if (WSAIoctl(nlsd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidAcceptEx, sizeof GuidAcceptEx, + &lpfnAcceptEx, sizeof lpfnAcceptEx, + &BytesRead, NULL, NULL) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(02322) + "winnt_accept: failed to retrieve AcceptEx, try 'AcceptFilter none'"); + return 1; + } + if (WSAIoctl(nlsd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidGetAcceptExSockaddrs, sizeof GuidGetAcceptExSockaddrs, + &lpfnGetAcceptExSockaddrs, sizeof lpfnGetAcceptExSockaddrs, + &BytesRead, NULL, NULL) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(02323) + "winnt_accept: failed to retrieve GetAcceptExSockaddrs, try 'AcceptFilter none'"); + return 1; + } + /* first, high priority event is an already accepted connection */ + events[1] = exit_event; + events[2] = max_requests_per_child_event; + } + else /* accf == ACCEPT_FILTER_NONE */ + { +reinit: /* target of connect upon too many AcceptEx failures */ + + /* last, low priority event is a not yet accepted connection */ + events[0] = exit_event; + events[1] = max_requests_per_child_event; + events[2] = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* The event needs to be removed from the accepted socket, + * if not removed from the listen socket prior to accept(), + */ + rv = WSAEventSelect(nlsd, events[2], FD_ACCEPT); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, + apr_get_netos_error(), ap_server_conf, APLOGNO(00333) + "WSAEventSelect() failed."); + CloseHandle(events[2]); + return 1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00334) + "Child: Accept thread listening on %pI using AcceptFilter %s", + lr->bind_addr, accept_filter_to_string(accf)); + + while (!shutdown_in_progress) { + if (!context) { + int timeout; + + context = mpm_get_completion_context(&timeout); + if (!context) { + if (!timeout) { + /* Hopefully a temporary condition in the provider? */ + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00335) + "winnt_accept: Too many failures grabbing a " + "connection ctx. Aborting."); + break; + } + } + Sleep(100); + continue; + } + } + + if (accf == ACCEPT_FILTER_CONNECT) + { + char *buf; + + /* Create and initialize the accept socket */ +#if APR_HAVE_IPV6 + if (context->accept_socket == INVALID_SOCKET) { + context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, + IPPROTO_TCP); + context->socket_family = ss_listen.ss_family; + } + else if (context->socket_family != ss_listen.ss_family) { + closesocket(context->accept_socket); + context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, + IPPROTO_TCP); + context->socket_family = ss_listen.ss_family; + } +#else + if (context->accept_socket == INVALID_SOCKET) + context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif + + if (context->accept_socket == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), + ap_server_conf, APLOGNO(00336) + "winnt_accept: Failed to allocate an accept socket. " + "Temporary resource constraint? Try again."); + Sleep(100); + continue; + } + + buf = context->buff; + + /* AcceptEx on the completion context. The completion context will be + * signaled when a connection is accepted. + */ + if (!lpfnAcceptEx(nlsd, context->accept_socket, buf, 0, + PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, &BytesRead, + &context->overlapped)) { + rv = apr_get_netos_error(); + if ((rv == APR_FROM_OS_ERROR(WSAECONNRESET)) || + (rv == APR_FROM_OS_ERROR(WSAEACCES))) { + /* We can get here when: + * 1) the client disconnects early + * 2) handshake was incomplete + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + continue; + } + else if ((rv == APR_FROM_OS_ERROR(WSAEINVAL)) || + (rv == APR_FROM_OS_ERROR(WSAENOTSOCK))) { + /* We can get here when: + * 1) TransmitFile does not properly recycle the accept socket (typically + * because the client disconnected) + * 2) there is VPN or Firewall software installed with + * buggy WSAAccept or WSADuplicateSocket implementation + * 3) the dynamic address / adapter has changed + * Give five chances, then fall back on AcceptFilter 'none' + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00337) + "Child: Encountered too many AcceptEx " + "faults accepting client connections. " + "Possible causes: dynamic address renewal, " + "or incompatible VPN or firewall software. "); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00338) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = ACCEPT_FILTER_NONE; + } + continue; + } + else if ((rv != APR_FROM_OS_ERROR(ERROR_IO_PENDING)) && + (rv != APR_FROM_OS_ERROR(WSA_IO_PENDING))) { + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00339) + "Child: Encountered too many AcceptEx " + "faults accepting client connections."); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00340) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = ACCEPT_FILTER_NONE; + goto reinit; + } + continue; + } + + err_count = 0; + events[0] = context->overlapped.hEvent; + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + if (rv == WAIT_OBJECT_0) { + if ((context->accept_socket != INVALID_SOCKET) && + !GetOverlappedResult((HANDLE)context->accept_socket, + &context->overlapped, + &BytesRead, FALSE)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + apr_get_os_error(), ap_server_conf, APLOGNO(00341) + "winnt_accept: Asynchronous AcceptEx failed."); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + } + } + else { + /* exit_event triggered or event handle was closed */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + break; + } + + if (context->accept_socket == INVALID_SOCKET) { + continue; + } + } + err_count = 0; + + /* Potential optimization; consider handing off to the worker */ + + /* Inherit the listen socket settings. Required for + * shutdown() to work + */ + if (setsockopt(context->accept_socket, SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, (char *)&nlsd, + sizeof(nlsd))) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), + ap_server_conf, APLOGNO(00342) + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed."); + /* Not a failure condition. Keep running. */ + } + + /* Get the local & remote address + * TODO; error check + */ + lpfnGetAcceptExSockaddrs(buf, 0, PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, + &context->sa_server, &context->sa_server_len, + &context->sa_client, &context->sa_client_len); + } + else /* accf == ACCEPT_FILTER_NONE */ + { + /* There is no socket reuse without AcceptEx() */ + if (context->accept_socket != INVALID_SOCKET) + closesocket(context->accept_socket); + + /* This could be a persistent event per-listener rather than + * per-accept. However, the event needs to be removed from + * the target socket if not removed from the listen socket + * prior to accept(), or the event select is inherited. + * and must be removed from the accepted socket. + */ + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + + if (rv != WAIT_OBJECT_0 + 2) { + /* not FD_ACCEPT; + * exit_event triggered or event handle was closed + */ + break; + } + + context->sa_server = (void *) context->buff; + context->sa_server_len = sizeof(context->buff) / 2; + context->sa_client_len = context->sa_server_len; + context->sa_client = (void *) (context->buff + + context->sa_server_len); + + context->accept_socket = accept(nlsd, context->sa_server, + &context->sa_server_len); + + if (context->accept_socket == INVALID_SOCKET) { + + rv = apr_get_netos_error(); + if ( rv == APR_FROM_OS_ERROR(WSAECONNRESET) + || rv == APR_FROM_OS_ERROR(WSAEINPROGRESS) + || rv == APR_FROM_OS_ERROR(WSAEWOULDBLOCK) ) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, + rv, ap_server_conf, APLOGNO(00343) + "accept() failed, retrying."); + continue; + } + + /* A more serious error than 'retry', log it */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + rv, ap_server_conf, APLOGNO(00344) + "accept() failed."); + + if ( rv == APR_FROM_OS_ERROR(WSAEMFILE) + || rv == APR_FROM_OS_ERROR(WSAENOBUFS) ) { + /* Hopefully a temporary condition in the provider? */ + Sleep(100); + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00345) + "Child: Encountered too many accept() " + "resource faults, aborting."); + break; + } + continue; + } + break; + } + /* Per MSDN, cancel the inherited association of this socket + * to the WSAEventSelect API, and restore the state corresponding + * to apr_os_sock_make's default assumptions (really, a flaw within + * os_sock_make and os_sock_put that it does not query). + */ + WSAEventSelect(context->accept_socket, 0, 0); + err_count = 0; + + context->sa_server_len = sizeof(context->buff) / 2; + if (getsockname(context->accept_socket, context->sa_server, + &context->sa_server_len) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00346) + "getsockname failed"); + continue; + } + if ((getpeername(context->accept_socket, context->sa_client, + &context->sa_client_len)) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00347) + "getpeername failed"); + memset(&context->sa_client, '\0', sizeof(context->sa_client)); + } + } + + sockinfo.os_sock = &context->accept_socket; + sockinfo.local = context->sa_server; + sockinfo.remote = context->sa_client; + sockinfo.family = context->sa_server->sa_family; + sockinfo.type = SOCK_STREAM; + sockinfo.protocol = IPPROTO_TCP; + /* Restore the state corresponding to apr_os_sock_make's default + * assumption of timeout -1 (really, a flaw of os_sock_make and + * os_sock_put that it does not query to determine ->timeout). + * XXX: Upon a fix to APR, these three statements should disappear. + */ + ioctlsocket(context->accept_socket, FIONBIO, &zero); + setsockopt(context->accept_socket, SOL_SOCKET, SO_RCVTIMEO, + (char *) &zero, sizeof(zero)); + setsockopt(context->accept_socket, SOL_SOCKET, SO_SNDTIMEO, + (char *) &zero, sizeof(zero)); + apr_os_sock_make(&context->sock, &sockinfo, context->ptrans); + + /* When a connection is received, send an io completion notification + * to the ThreadDispatchIOCP. + */ + PostQueuedCompletionStatus(ThreadDispatchIOCP, BytesRead, + IOCP_CONNECTION_ACCEPTED, + &context->overlapped); + context = NULL; + } + if (accf == ACCEPT_FILTER_NONE) + CloseHandle(events[2]); + + if (!shutdown_in_progress) { + /* Yow, hit an irrecoverable error! Tell the child to die. */ + SetEvent(exit_event); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00348) + "Child: Accept thread exiting."); + return 0; +} + + +static winnt_conn_ctx_t *winnt_get_connection(winnt_conn_ctx_t *context) +{ + int rc; + DWORD BytesRead; + LPOVERLAPPED pol; +#ifdef _WIN64 + ULONG_PTR CompKey; +#else + DWORD CompKey; +#endif + + mpm_recycle_completion_context(context); + + apr_atomic_inc32(&g_blocked_threads); + while (1) { + if (workers_may_exit) { + apr_atomic_dec32(&g_blocked_threads); + return NULL; + } + rc = GetQueuedCompletionStatus(ThreadDispatchIOCP, &BytesRead, + &CompKey, &pol, INFINITE); + if (!rc) { + rc = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, ap_server_conf, APLOGNO(00349) + "Child: GetQueuedCompletionStatus returned %d", + rc); + continue; + } + + switch (CompKey) { + case IOCP_CONNECTION_ACCEPTED: + context = CONTAINING_RECORD(pol, winnt_conn_ctx_t, overlapped); + break; + case IOCP_SHUTDOWN: + apr_atomic_dec32(&g_blocked_threads); + return NULL; + default: + apr_atomic_dec32(&g_blocked_threads); + return NULL; + } + break; + } + apr_atomic_dec32(&g_blocked_threads); + + return context; +} + +/* + * worker_main() + * Main entry point for the worker threads. Worker threads block in + * win*_get_connection() awaiting a connection to service. + */ +static DWORD __stdcall worker_main(void *thread_num_val) +{ + apr_thread_t *thd = NULL; + apr_os_thread_t osthd = NULL; + static int requests_this_child = 0; + winnt_conn_ctx_t *context = NULL; + int thread_num = (int)thread_num_val; + ap_sb_handle_t *sbh; + conn_rec *c; + apr_int32_t disconnected; + +#if AP_HAS_THREAD_LOCAL + if (ap_thread_current_create(&thd, NULL, pchild) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(10376) + "Couldn't initialize worker thread, thread locals won't " + "be available"); + osthd = apr_os_thread_current(); + } +#else + osthd = apr_os_thread_current(); +#endif + + while (1) { + + ap_update_child_status_from_indexes(0, thread_num, SERVER_READY, NULL); + + /* Grab a connection off the network */ + context = winnt_get_connection(context); + + if (!context) { + /* Time for the thread to exit */ + break; + } + + /* Have we hit MaxConnectionsPerChild connections? */ + if (ap_max_requests_per_child) { + requests_this_child++; + if (requests_this_child > ap_max_requests_per_child) { + SetEvent(max_requests_per_child_event); + } + } + + ap_create_sb_handle(&sbh, context->ptrans, 0, thread_num); + c = ap_run_create_connection(context->ptrans, ap_server_conf, + context->sock, thread_num, sbh, + context->ba); + + if (!c) { + /* ap_run_create_connection closes the socket on failure */ + context->accept_socket = INVALID_SOCKET; + continue; + } + + if (osthd) { + thd = NULL; + apr_os_thread_put(&thd, &osthd, context->ptrans); + } + c->current_thread = thd; + + ap_process_connection(c, context->sock); + + ap_lingering_close(c); + + apr_socket_opt_get(context->sock, APR_SO_DISCONNECTED, &disconnected); + if (!disconnected) { + context->accept_socket = INVALID_SOCKET; + } + } + + ap_update_child_status_from_indexes(0, thread_num, SERVER_DEAD, NULL); + +#if AP_HAS_THREAD_LOCAL + if (!osthd) { + apr_pool_destroy(apr_thread_pool_get(thd)); + } +#endif + + return 0; +} + + +static void cleanup_thread(HANDLE *handles, int *thread_cnt, + int thread_to_clean) +{ + int i; + + CloseHandle(handles[thread_to_clean]); + for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++) + handles[i] = handles[i + 1]; + (*thread_cnt)--; +} + + +/* + * child_main() + * Entry point for the main control thread for the child process. + * This thread creates the accept thread, worker threads and + * monitors the child process for maintenance and shutdown + * events. + */ +static void create_listener_thread(void) +{ + unsigned tid; + int num_listeners = 0; + /* Start an accept thread per listener + * XXX: Why would we have a NULL sd in our listeners? + */ + ap_listen_rec *lr; + + /* Number of completion_contexts allowed in the system is + * (ap_threads_per_child + num_listeners). We need the additional + * completion contexts to prevent server hangs when ThreadsPerChild + * is configured to something less than or equal to the number + * of listeners. This is not a usual case, but people have + * encountered it. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + num_listeners++; + } + max_num_completion_contexts = ap_threads_per_child + num_listeners; + + /* Now start a thread per listener */ + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->sd != NULL) { + /* A smaller stack is sufficient. + * To convert to CreateThread, the returned handle cannot be + * ignored, it must be closed/joined. + */ + _beginthreadex(NULL, 65536, winnt_accept, + (void *) lr, stack_res_flag, &tid); + } + } +} + + +void child_main(apr_pool_t *pconf, DWORD parent_pid) +{ + apr_status_t status; + apr_hash_t *ht; + ap_listen_rec *lr; + HANDLE child_events[3]; + HANDLE *child_handles; + int listener_started = 0; + int threads_created = 0; + int watch_thread; + int time_remains; + int cld; + DWORD tid; + int rv; + int i; + int num_events; + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + ht = apr_hash_make(pchild); + + /* Initialize the child_events */ + max_requests_per_child_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!max_requests_per_child_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00350) + "Child: Failed to create a max_requests event."); + exit(APEXIT_CHILDINIT); + } + child_events[0] = exit_event; + child_events[1] = max_requests_per_child_event; + + if (parent_pid != my_pid) { + child_events[2] = OpenProcess(SYNCHRONIZE, FALSE, parent_pid); + if (child_events[2] == NULL) { + num_events = 2; + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(02643) + "Child: Failed to open handle to parent process %ld; " + "will not react to abrupt parent termination", parent_pid); + } + else { + num_events = 3; + } + } + else { + /* presumably -DONE_PROCESS */ + child_events[2] = NULL; + num_events = 2; + } + + /* + * Wait until we have permission to start accepting connections. + * start_mutex is used to ensure that only one child ever + * goes into the listen/accept loop at once. + */ + status = apr_proc_mutex_lock(start_mutex); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(00351) + "Child: Failed to acquire the start_mutex. " + "Process will exit."); + exit(APEXIT_CHILDINIT); + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00352) + "Child: Acquired the start mutex."); + + /* + * Create the worker thread dispatch IOCompletionPort + */ + /* Create the worker thread dispatch IOCP */ + ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, 0, 0); + apr_thread_mutex_create(&qlock, APR_THREAD_MUTEX_DEFAULT, pchild); + qwait_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!qwait_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00353) + "Child: Failed to create a qwait event."); + exit(APEXIT_CHILDINIT); + } + + /* + * Create the pool of worker threads + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00354) + "Child: Starting %d worker threads.", ap_threads_per_child); + child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child + * sizeof(HANDLE)); + apr_thread_mutex_create(&child_lock, APR_THREAD_MUTEX_DEFAULT, pchild); + + while (1) { + for (i = 0; i < ap_threads_per_child; i++) { + int *score_idx; + int status = ap_scoreboard_image->servers[0][i].status; + if (status != SERVER_GRACEFUL && status != SERVER_DEAD) { + continue; + } + ap_update_child_status_from_indexes(0, i, SERVER_STARTING, NULL); + + child_handles[i] = CreateThread(NULL, ap_thread_stacksize, + worker_main, (void *) i, + stack_res_flag, &tid); + if (child_handles[i] == 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00355) + "Child: CreateThread failed. Unable to " + "create all worker threads. Created %d of the %d " + "threads requested with the ThreadsPerChild " + "configuration directive.", + threads_created, ap_threads_per_child); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + goto shutdown; + } + threads_created++; + /* Save the score board index in ht keyed to the thread handle. + * We need this when cleaning up threads down below... + */ + apr_thread_mutex_lock(child_lock); + score_idx = apr_pcalloc(pchild, sizeof(int)); + *score_idx = i; + apr_hash_set(ht, &child_handles[i], sizeof(HANDLE), score_idx); + apr_thread_mutex_unlock(child_lock); + } + /* Start the listener only when workers are available */ + if (!listener_started && threads_created) { + create_listener_thread(); + listener_started = 1; + winnt_mpm_state = AP_MPMQ_RUNNING; + } + if (threads_created == ap_threads_per_child) { + break; + } + /* Check to see if the child has been told to exit */ + if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { + break; + } + /* wait for previous generation to clean up an entry in the scoreboard + */ + apr_sleep(1 * APR_USEC_PER_SEC); + } + + /* Wait for one of these events: + * exit_event: + * The exit_event is signaled by the parent process to notify + * the child that it is time to exit. + * + * max_requests_per_child_event: + * This event is signaled by the worker threads to indicate that + * the process has handled MaxConnectionsPerChild connections. + * + * parent process exiting + * + * TIMEOUT: + * To do periodic maintenance on the server (check for thread exits, + * number of completion contexts, etc.) + * + * XXX: thread exits *aren't* being checked. + * + * XXX: other_child - we need the process handles to the other children + * in order to map them to apr_proc_other_child_read (which is not + * named well, it's more like a_p_o_c_died.) + * + * XXX: however - if we get a_p_o_c handle inheritance working, and + * the parent process creates other children and passes the pipes + * to our worker processes, then we have no business doing such + * things in the child_main loop, but should happen in master_main. + */ + while (1) { +#if !APR_HAS_OTHER_CHILD + rv = WaitForMultipleObjects(num_events, (HANDLE *)child_events, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; +#else + /* THIS IS THE EXPECTED BUILD VARIATION -- APR_HAS_OTHER_CHILD */ + rv = WaitForMultipleObjects(num_events, (HANDLE *)child_events, FALSE, 1000); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_TIMEOUT) { + apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); + } + else +#endif + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00356) + "Child: WAIT_FAILED -- shutting down server"); + /* check handle validity to identify a possible culprit */ + for (i = 0; i < num_events; i++) { + DWORD out_flags; + + if (0 == GetHandleInformation(child_events[i], &out_flags)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(02644) + "Child: Event handle #%d (%pp) is invalid", + i, child_events[i]); + } + } + break; + } + else if (cld == 0) { + /* Exit event was signaled */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00357) + "Child: Exit event signaled. Child process is " + "ending."); + break; + } + else if (cld == 2) { + /* The parent is dead. Shutdown the child process. */ + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(02538) + "Child: Parent process exited abruptly. Child process " + "is ending"); + break; + } + else { + /* MaxConnectionsPerChild event set by the worker threads. + * Signal the parent to restart + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00358) + "Child: Process exiting because it reached " + "MaxConnectionsPerChild. Signaling the parent to " + "restart a new child process."); + ap_signal_parent(SIGNAL_PARENT_RESTART); + break; + } + } + + /* + * Time to shutdown the child process + */ + + shutdown: + + winnt_mpm_state = AP_MPMQ_STOPPING; + + /* Close the listening sockets. Note, we must close the listeners + * before closing any accept sockets pending in AcceptEx to prevent + * memory leaks in the kernel. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + apr_socket_close(lr->sd); + } + + /* Shutdown listener threads and pending AcceptEx sockets + * but allow the worker threads to continue consuming from + * the queue of accepted connections. + */ + shutdown_in_progress = 1; + + Sleep(1000); + + /* Tell the worker threads to exit */ + workers_may_exit = 1; + + /* Release the start_mutex to let the new process (in the restart + * scenario) a chance to begin accepting and servicing requests + */ + rv = apr_proc_mutex_unlock(start_mutex); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00359) + "Child: Released the start mutex"); + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00360) + "Child: Failure releasing the start mutex"); + } + + /* Shutdown the worker threads + * Post worker threads blocked on the ThreadDispatch IOCompletion port + */ + while (g_blocked_threads > 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00361) + "Child: %d threads blocked on the completion port", + g_blocked_threads); + for (i=g_blocked_threads; i > 0; i--) { + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, + IOCP_SHUTDOWN, NULL); + } + Sleep(1000); + } + /* Empty the accept queue of completion contexts */ + apr_thread_mutex_lock(qlock); + while (qhead) { + CloseHandle(qhead->overlapped.hEvent); + closesocket(qhead->accept_socket); + qhead = qhead->next; + } + apr_thread_mutex_unlock(qlock); + + /* Give busy threads a chance to service their connections + * (no more than the global server timeout period which + * we track in msec remaining). + */ + watch_thread = 0; + time_remains = (int)(ap_server_conf->timeout / APR_TIME_C(1000)); + + while (threads_created) + { + int nFailsafe = MAXIMUM_WAIT_OBJECTS; + DWORD dwRet; + + /* Every time we roll over to wait on the first group + * of MAXIMUM_WAIT_OBJECTS threads, take a breather, + * and infrequently update the error log. + */ + if (watch_thread >= threads_created) { + if ((time_remains -= 100) < 0) + break; + + /* Every 30 seconds give an update */ + if ((time_remains % 30000) == 0) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, + ap_server_conf, APLOGNO(00362) + "Child: Waiting %d more seconds " + "for %d worker threads to finish.", + time_remains / 1000, threads_created); + } + /* We'll poll from the top, 10 times per second */ + Sleep(100); + watch_thread = 0; + } + + /* Fairness, on each iteration we will pick up with the thread + * after the one we just removed, even if it's a single thread. + * We don't block here. + */ + dwRet = WaitForMultipleObjects(min(threads_created - watch_thread, + MAXIMUM_WAIT_OBJECTS), + child_handles + watch_thread, 0, 0); + + if (dwRet == WAIT_FAILED) { + break; + } + if (dwRet == WAIT_TIMEOUT) { + /* none ready */ + watch_thread += MAXIMUM_WAIT_OBJECTS; + continue; + } + else if (dwRet >= WAIT_ABANDONED_0) { + /* We just got the ownership of the object, which + * should happen at most MAXIMUM_WAIT_OBJECTS times. + * It does NOT mean that the object is signaled. + */ + if ((nFailsafe--) < 1) + break; + } + else { + watch_thread += (dwRet - WAIT_OBJECT_0); + if (watch_thread >= threads_created) + break; + cleanup_thread(child_handles, &threads_created, watch_thread); + } + } + + /* Kill remaining threads off the hard way */ + if (threads_created) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00363) + "Child: Terminating %d threads that failed to exit.", + threads_created); + } + for (i = 0; i < threads_created; i++) { + int *idx; + TerminateThread(child_handles[i], 1); + CloseHandle(child_handles[i]); + /* Reset the scoreboard entry for the thread we just whacked */ + idx = apr_hash_get(ht, &child_handles[i], sizeof(HANDLE)); + if (idx) { + ap_update_child_status_from_indexes(0, *idx, SERVER_DEAD, NULL); + } + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00364) + "Child: All worker threads have exited."); + + apr_thread_mutex_destroy(child_lock); + apr_thread_mutex_destroy(qlock); + CloseHandle(qwait_event); + CloseHandle(ThreadDispatchIOCP); + + apr_pool_destroy(pchild); + CloseHandle(exit_event); + if (child_events[2] != NULL) { + CloseHandle(child_events[2]); + } +} + +#endif /* def WIN32 */ diff --git a/server/mpm/winnt/config.m4 b/server/mpm/winnt/config.m4 new file mode 100644 index 0000000..5c1fd42 --- /dev/null +++ b/server/mpm/winnt/config.m4 @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(if WinNT MPM supports this platform) +case $host in + *mingw32*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(winnt, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/winnt/config3.m4 b/server/mpm/winnt/config3.m4 new file mode 100644 index 0000000..f937e40 --- /dev/null +++ b/server/mpm/winnt/config3.m4 @@ -0,0 +1,2 @@ +winnt_objects="child.lo mpm_winnt.lo nt_eventlog.lo service.lo" +APACHE_MPM_MODULE(winnt, $enable_mpm_winnt, $winnt_objects) diff --git a/server/mpm/winnt/mpm_default.h b/server/mpm/winnt/mpm_default.h new file mode 100644 index 0000000..b5350d4 --- /dev/null +++ b/server/mpm/winnt/mpm_default.h @@ -0,0 +1,60 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file winnt/mpm_default.h + * @brief win32 MPM defaults + * + * @defgroup APACHE_MPM_WINNT WinNT MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Default limit on the maximum setting of the ThreadsPerChild configuration + * directive. This limit can be overridden with the ThreadLimit directive. + * This limit directly influences the amount of shared storage that is allocated + * for the scoreboard. DEFAULT_THREAD_LIMIT represents a good compromise + * between scoreboard size and the ability of the server to handle the most + * common installation requirements. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 1920 +#endif + +/* The ThreadLimit directive can be used to override the DEFAULT_THREAD_LIMIT. + * ThreadLimit cannot be tuned larger than MAX_THREAD_LIMIT. + * This is a sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 15000 +#endif + +/* Number of threads started in the child process in the absence + * of a ThreadsPerChild configuration directive + */ +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 64 +#endif + +/* Max number of child processes allowed. + */ +#define HARD_SERVER_LIMIT 1 + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/winnt/mpm_winnt.c b/server/mpm/winnt/mpm_winnt.c new file mode 100644 index 0000000..1b8962e --- /dev/null +++ b/server/mpm/winnt/mpm_winnt.c @@ -0,0 +1,1783 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WIN32 + +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "apr_general.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" +#include "scoreboard.h" + +#ifdef __WATCOMC__ +#define _environ environ +#endif + +#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION /* missing on MinGW */ +#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 +#endif + +/* Because ap_setup_listeners() is skipped in the child, any merging + * of [::]:80 and 0.0.0.0:80 for AP_ENABLE_V4_MAPPED in the parent + * won't have taken place in the child, so the child will expect to + * read two sockets for "Listen 80" but the parent will send only + * one. + */ +#ifdef AP_ENABLE_V4_MAPPED +#error The WinNT MPM does not currently support AP_ENABLE_V4_MAPPED +#endif + +/* scoreboard.c does the heavy lifting; all we do is create the child + * score by moving a handle down the pipe into the child's stdin. + */ +extern apr_shm_t *ap_scoreboard_shm; + +/* my_generation is returned to the scoreboard code */ +static volatile ap_generation_t my_generation=0; + +/* Definitions of WINNT MPM specific config globals */ +static HANDLE shutdown_event; /* used to signal the parent to shutdown */ +static HANDLE restart_event; /* used to signal the parent to restart */ + +static int one_process = 0; +static char const* signal_arg = NULL; + +OSVERSIONINFO osver; /* VER_PLATFORM_WIN32_NT */ + +/* set by child_main to STACK_SIZE_PARAM_IS_A_RESERVATION for NT >= 5.1 (XP/2003) */ +DWORD stack_res_flag; + +static DWORD parent_pid; +DWORD my_pid; + +/* used by parent to signal the child to start and exit */ +apr_proc_mutex_t *start_mutex; +HANDLE exit_event; + +int ap_threads_per_child = 0; +static int thread_limit = 0; +static int first_thread_limit = 0; +int winnt_mpm_state = AP_MPMQ_STARTING; + +/* shared by service.c as global, although + * perhaps it should be private. + */ +apr_pool_t *pconf; + +/* Only one of these, the pipe from our parent, meant only for + * one child worker's consumption (not to be inherited!) + * XXX: decorate this name for the trunk branch, was left simplified + * only to make the 2.2 patch trivial to read. + */ +static HANDLE pipe; + +/* + * Command processors + */ + +static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_per_child = atoi(arg); + return NULL; +} +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + thread_limit = atoi(arg); + return NULL; +} + +static const command_rec winnt_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates" ), +AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum worker threads in a server for this run of Apache"), +{ NULL } +}; + +static void winnt_note_child_started(int slot, pid_t pid) +{ + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = my_generation; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + my_generation, slot, MPM_CHILD_STARTED); +} + +static void winnt_note_child_killed(int slot) +{ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation, + slot, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[slot].pid = 0; +} + +/* + * Signalling Apache on NT. + * + * Under Unix, Apache can be told to shutdown or restart by sending various + * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so + * we use "events" instead. The parent apache process goes into a loop + * where it waits forever for a set of events. Two of those events are + * called + * + * apPID_shutdown + * apPID_restart + * + * (where PID is the PID of the apache parent process). When one of these + * is signalled, the Apache parent performs the appropriate action. The events + * can become signalled through internal Apache methods (e.g. if the child + * finds a fatal error and needs to kill its parent), via the service + * control manager (the control thread will signal the shutdown event when + * requested to stop the Apache service), from the -k Apache command line, + * or from any external program which finds the Apache PID from the + * httpd.pid file. + * + * The signal_parent() function, below, is used to signal one of these events. + * It can be called by any child or parent process, since it does not + * rely on global variables. + * + * On entry, type gives the event to signal. 0 means shutdown, 1 means + * graceful restart. + */ +/* + * Initialise the signal names, in the global variables signal_name_prefix, + * signal_restart_name and signal_shutdown_name. + */ +#define MAX_SIGNAL_NAME 30 /* Long enough for apPID_shutdown, where PID is an int */ +static char signal_name_prefix[MAX_SIGNAL_NAME]; +static char signal_restart_name[MAX_SIGNAL_NAME]; +static char signal_shutdown_name[MAX_SIGNAL_NAME]; +static void setup_signal_names(char *prefix) +{ + apr_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix); + apr_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name), + "%s_shutdown", signal_name_prefix); + apr_snprintf(signal_restart_name, sizeof(signal_restart_name), + "%s_restart", signal_name_prefix); +} + +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type) +{ + HANDLE e; + char *signal_name; + + if (parent_pid == my_pid) { + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + SetEvent(shutdown_event); + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + SetEvent(restart_event); + break; + } + } + return; + } + + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + signal_name = signal_shutdown_name; + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + signal_name = signal_restart_name; + break; + } + default: + return; + } + + e = OpenEvent(EVENT_MODIFY_STATE, FALSE, signal_name); + if (!e) { + /* Um, problem, can't signal the parent, which means we can't + * signal ourselves to die. Ignore for now... + */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00382) + "OpenEvent on %s event", signal_name); + return; + } + if (SetEvent(e) == 0) { + /* Same problem as above */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00383) + "SetEvent on %s event", signal_name); + CloseHandle(e); + return; + } + CloseHandle(e); +} + + +/* + * Passed the following handles [in sync with send_handles_to_child()] + * + * ready event [signal the parent immediately, then close] + * exit event [save to poll later] + * start mutex [signal from the parent to begin accept()] + * scoreboard shm handle [to recreate the ap_scoreboard] + */ +static void get_handles_from_parent(server_rec *s, HANDLE *child_exit_event, + apr_proc_mutex_t **child_start_mutex, + apr_shm_t **scoreboard_shm) +{ + HANDLE hScore; + HANDLE ready_event; + HANDLE os_start; + DWORD BytesRead; + void *sb_shared; + apr_status_t rv; + + /* *** We now do this way back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + if (!ReadFile(pipe, &ready_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00384) + "Child: Unable to retrieve the ready event from the parent"); + exit(APEXIT_CHILDINIT); + } + + SetEvent(ready_event); + CloseHandle(ready_event); + + if (!ReadFile(pipe, child_exit_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00385) + "Child: Unable to retrieve the exit event from the parent"); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &os_start, sizeof(os_start), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(os_start))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00386) + "Child: Unable to retrieve the start_mutex from the parent"); + exit(APEXIT_CHILDINIT); + } + *child_start_mutex = NULL; + if ((rv = apr_os_proc_mutex_put(child_start_mutex, &os_start, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00387) + "Child: Unable to access the start_mutex from the parent"); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &hScore, sizeof(hScore), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(hScore))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00388) + "Child: Unable to retrieve the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + *scoreboard_shm = NULL; + if ((rv = apr_os_shm_put(scoreboard_shm, &hScore, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00389) + "Child: Unable to access the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + + rv = ap_reopen_scoreboard(s->process->pool, scoreboard_shm, 1); + if (rv || !(sb_shared = apr_shm_baseaddr_get(*scoreboard_shm))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00390) + "Child: Unable to reopen the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + /* We must 'initialize' the scoreboard to relink all the + * process-local pointer arrays into the shared memory block. + */ + ap_init_scoreboard(sb_shared); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00391) + "Child: Retrieved our scoreboard from the parent."); +} + + +static int send_handles_to_child(apr_pool_t *p, + HANDLE child_ready_event, + HANDLE child_exit_event, + apr_proc_mutex_t *child_start_mutex, + apr_shm_t *scoreboard_shm, + HANDLE hProcess, + apr_file_t *child_in) +{ + apr_status_t rv; + HANDLE hCurrentProcess = GetCurrentProcess(); + HANDLE hDup; + HANDLE os_start; + HANDLE hScore; + apr_size_t BytesWritten; + + if ((rv = apr_file_write_full(child_in, &my_generation, + sizeof(my_generation), NULL)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(02964) + "Parent: Unable to send its generation to the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, child_ready_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00392) + "Parent: Unable to duplicate the ready event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00393) + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, child_exit_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00394) + "Parent: Unable to duplicate the exit event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00395) + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if ((rv = apr_os_proc_mutex_get(&os_start, child_start_mutex)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00396) + "Parent: Unable to retrieve the start mutex for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, os_start, hProcess, &hDup, + SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00397) + "Parent: Unable to duplicate the start mutex to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00398) + "Parent: Unable to send the start mutex to the child"); + return -1; + } + if ((rv = apr_os_shm_get(&hScore, scoreboard_shm)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00399) + "Parent: Unable to retrieve the scoreboard handle for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, hScore, hProcess, &hDup, + FILE_MAP_READ | FILE_MAP_WRITE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00400) + "Parent: Unable to duplicate the scoreboard handle to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00401) + "Parent: Unable to send the scoreboard handle to the child"); + return -1; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00402) + "Parent: Sent the scoreboard to the child"); + return 0; +} + + +/* + * get_listeners_from_parent() + * The listen sockets are opened in the parent. This function, which runs + * exclusively in the child process, receives them from the parent and + * makes them available in the child. + */ +static void get_listeners_from_parent(server_rec *s) +{ + WSAPROTOCOL_INFO WSAProtocolInfo; + ap_listen_rec *lr; + DWORD BytesRead; + int lcnt = 0; + SOCKET nsd; + + /* Set up a default listener if necessary */ + if (ap_listeners == NULL) { + ap_listen_rec *lr; + lr = apr_palloc(s->process->pool, sizeof(ap_listen_rec)); + lr->sd = NULL; + lr->next = ap_listeners; + ap_listeners = lr; + } + + /* Open the pipe to the parent process to receive the inherited socket + * data. The sockets have been set to listening in the parent process. + * + * *** We now do this way back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00403) + "Child: Waiting for data for listening socket %pI", + lr->bind_addr); + if (!ReadFile(pipe, &WSAProtocolInfo, sizeof(WSAPROTOCOL_INFO), + &BytesRead, (LPOVERLAPPED) NULL)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00404) + "Child: Unable to read socket data from parent"); + exit(APEXIT_CHILDINIT); + } + + nsd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, + &WSAProtocolInfo, 0, 0); + if (nsd == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00405) + "Child: WSASocket failed to open the inherited socket"); + exit(APEXIT_CHILDINIT); + } + + if (!SetHandleInformation((HANDLE)nsd, HANDLE_FLAG_INHERIT, 0)) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00406) + "Child: SetHandleInformation failed"); + } + apr_os_sock_put(&lr->sd, &nsd, s->process->pool); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00407) + "Child: retrieved %d listeners from parent", lcnt); +} + + +static int send_listeners_to_child(apr_pool_t *p, DWORD dwProcessId, + apr_file_t *child_in) +{ + apr_status_t rv; + int lcnt = 0; + ap_listen_rec *lr; + LPWSAPROTOCOL_INFO lpWSAProtocolInfo; + apr_size_t BytesWritten; + + /* Run the chain of open sockets. For each socket, duplicate it + * for the target process then send the WSAPROTOCOL_INFO + * (returned by dup socket) to the child. + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + apr_os_sock_t nsd; + lpWSAProtocolInfo = apr_pcalloc(p, sizeof(WSAPROTOCOL_INFO)); + apr_os_sock_get(&nsd, lr->sd); + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00408) + "Parent: Duplicating socket %d (%pI) and sending it to child process %lu", + nsd, lr->bind_addr, dwProcessId); + if (WSADuplicateSocket(nsd, dwProcessId, + lpWSAProtocolInfo) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00409) + "Parent: WSADuplicateSocket failed for socket %d. Check the FAQ.", nsd); + return -1; + } + + if ((rv = apr_file_write_full(child_in, lpWSAProtocolInfo, + sizeof(WSAPROTOCOL_INFO), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00410) + "Parent: Unable to write duplicated socket %d to the child.", nsd); + return -1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00411) + "Parent: Sent %d listeners to child %lu", lcnt, dwProcessId); + return 0; +} + +enum waitlist_e { + waitlist_ready = 0, + waitlist_term = 1 +}; + +static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_event, + DWORD *child_pid) +{ + /* These NEVER change for the lifetime of this parent + */ + static char **args = NULL; + static char pidbuf[28]; + + apr_status_t rv; + apr_pool_t *ptemp; + apr_procattr_t *attr; + apr_proc_t new_child; + HANDLE hExitEvent; + HANDLE waitlist[2]; /* see waitlist_e */ + char *cmd; + char *cwd; + char **env; + int envc; + + apr_pool_create_ex(&ptemp, p, NULL, NULL); + apr_pool_tag(ptemp, "create_process"); + + /* Build the command line. Should look something like this: + * C:/apache/bin/httpd.exe -f ap_server_confname + * First, get the path to the executable... + */ + apr_procattr_create(&attr, ptemp); + apr_procattr_cmdtype_set(attr, APR_PROGRAM); + apr_procattr_detach_set(attr, 1); + if (((rv = apr_filepath_get(&cwd, 0, ptemp)) != APR_SUCCESS) + || ((rv = apr_procattr_dir_set(attr, cwd)) != APR_SUCCESS)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00412) + "Parent: Failed to get the current path"); + } + + if (!args) { + /* Build the args array, only once since it won't change + * for the lifetime of this parent process. + */ + if ((rv = ap_os_proc_filepath(&cmd, ptemp)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, ERROR_BAD_PATHNAME, ap_server_conf, APLOGNO(00413) + "Parent: Failed to get full path of %s", + ap_server_conf->process->argv[0]); + apr_pool_destroy(ptemp); + return -1; + } + + args = ap_malloc((ap_server_conf->process->argc + 1) * sizeof (char*)); + memcpy(args + 1, ap_server_conf->process->argv + 1, + (ap_server_conf->process->argc - 1) * sizeof (char*)); + args[0] = ap_malloc(strlen(cmd) + 1); + strcpy(args[0], cmd); + args[ap_server_conf->process->argc] = NULL; + } + else { + cmd = args[0]; + } + + /* Create a pipe to send handles to the child */ + if ((rv = apr_procattr_io_set(attr, APR_FULL_BLOCK, + APR_NO_PIPE, APR_NO_PIPE)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00414) + "Parent: Unable to create child stdin pipe."); + apr_pool_destroy(ptemp); + return -1; + } + + /* Create the child_ready_event */ + waitlist[waitlist_ready] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!waitlist[waitlist_ready]) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00415) + "Parent: Could not create ready event for child process"); + apr_pool_destroy (ptemp); + return -1; + } + + /* Create the child_exit_event */ + hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hExitEvent) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00416) + "Parent: Could not create exit event for child process"); + apr_pool_destroy(ptemp); + CloseHandle(waitlist[waitlist_ready]); + return -1; + } + + /* Build the env array */ + for (envc = 0; _environ[envc]; ++envc) { + ; + } + env = apr_palloc(ptemp, (envc + 2) * sizeof (char*)); + memcpy(env, _environ, envc * sizeof (char*)); + apr_snprintf(pidbuf, sizeof(pidbuf), "AP_PARENT_PID=%lu", parent_pid); + env[envc] = pidbuf; + env[envc + 1] = NULL; + + rv = apr_proc_create(&new_child, cmd, (const char * const *)args, + (const char * const *)env, attr, ptemp); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00417) + "Parent: Failed to create the child process."); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00418) + "Parent: Created child process %d", new_child.pid); + + if (send_handles_to_child(ptemp, waitlist[waitlist_ready], hExitEvent, + start_mutex, ap_scoreboard_shm, + new_child.hproc, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + + /* Important: + * Give the child process a chance to run before dup'ing the sockets. + * We have already set the listening sockets noninheritable, but if + * WSADuplicateSocket runs before the child process initializes + * the listeners will be inherited anyway. + */ + waitlist[waitlist_term] = new_child.hproc; + rv = WaitForMultipleObjects(2, waitlist, FALSE, INFINITE); + CloseHandle(waitlist[waitlist_ready]); + if (rv != WAIT_OBJECT_0) { + /* + * Outch... that isn't a ready signal. It's dead, Jim! + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + if (send_listeners_to_child(ptemp, new_child.pid, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + apr_file_close(new_child.in); + + *child_exit_event = hExitEvent; + *child_proc = new_child.hproc; + *child_pid = new_child.pid; + + return 0; +} + +/*********************************************************************** + * master_main() + * master_main() runs in the parent process. It creates the child + * process which handles HTTP requests then waits on one of three + * events: + * + * restart_event + * ------------- + * The restart event causes master_main to start a new child process and + * tells the old child process to exit (by setting the child_exit_event). + * The restart event is set as a result of one of the following: + * 1. An apache -k restart command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_RESTART) + * call by code in service.c. + * 3. The child process calling ap_signal_parent(SIGNAL_PARENT_RESTART) + * as a result of hitting MaxConnectionsPerChild. + * + * shutdown_event + * -------------- + * The shutdown event causes master_main to tell the child process to + * exit and that the server is shutting down. The shutdown event is + * set as a result of one of the following: + * 1. An apache -k shutdown command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_SHUTDOWN) + * call by code in service.c. + * + * child process handle + * -------------------- + * The child process handle will be signaled if the child process + * exits for any reason. In a normal running server, the signaling + * of this event means that the child process has exited prematurely + * due to a seg fault or other irrecoverable error. For server + * robustness, master_main will restart the child process under this + * condition. + * + * master_main uses the child_exit_event to signal the child process + * to exit. + **********************************************************************/ +#define NUM_WAIT_HANDLES 3 +#define CHILD_HANDLE 0 +#define SHUTDOWN_HANDLE 1 +#define RESTART_HANDLE 2 +static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_event) +{ + int rv, cld; + int child_created; + int restart_pending; + int shutdown_pending; + HANDLE child_exit_event; + HANDLE event_handles[NUM_WAIT_HANDLES]; + DWORD child_pid; + + child_created = restart_pending = shutdown_pending = 0; + + event_handles[SHUTDOWN_HANDLE] = shutdown_event; + event_handles[RESTART_HANDLE] = restart_event; + + /* Create a single child process */ + rv = create_process(pconf, &event_handles[CHILD_HANDLE], + &child_exit_event, &child_pid); + if (rv < 0) + { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00419) + "master_main: create child process failed. Exiting."); + shutdown_pending = 1; + goto die_now; + } + + child_created = 1; + + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_started(); + } + + /* Update the scoreboard. Note that there is only a single active + * child at once. + */ + ap_scoreboard_image->parent[0].quiescing = 0; + winnt_note_child_started(/* slot */ 0, child_pid); + + /* Wait for shutdown or restart events or for child death */ + winnt_mpm_state = AP_MPMQ_RUNNING; + rv = WaitForMultipleObjects(NUM_WAIT_HANDLES, (HANDLE *) event_handles, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK,APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00420) + "master_main: WaitForMultipleObjects WAIT_FAILED -- doing server shutdown"); + shutdown_pending = 1; + } + else if (rv == WAIT_TIMEOUT) { + /* Hey, this cannot happen */ + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00421) + "master_main: WaitForMultipleObjects with INFINITE wait exited with WAIT_TIMEOUT"); + shutdown_pending = 1; + } + else if (cld == SHUTDOWN_HANDLE) { + /* shutdown_event signalled */ + shutdown_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, s, APLOGNO(00422) + "Parent: Received shutdown signal -- Shutting down the server."); + if (ResetEvent(shutdown_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00423) + "ResetEvent(shutdown_event)"); + } + } + else if (cld == RESTART_HANDLE) { + /* Received a restart event. Prepare the restart_event to be reused + * then signal the child process to exit. + */ + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(00424) + "Parent: Received restart signal -- Restarting the server."); + if (ResetEvent(restart_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00425) + "Parent: ResetEvent(restart_event) failed."); + } + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00426) + "Parent: SetEvent for child process event %pp failed.", + event_handles[CHILD_HANDLE]); + } + /* Don't wait to verify that the child process really exits, + * just move on with the restart. + */ + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + /* The child process exited prematurely due to a fatal error. */ + DWORD exitcode; + if (!GetExitCodeProcess(event_handles[CHILD_HANDLE], &exitcode)) { + /* HUH? We did exit, didn't we? */ + exitcode = APEXIT_CHILDFATAL; + } + if ( exitcode == APEXIT_CHILDFATAL + || exitcode == APEXIT_CHILDINIT + || exitcode == APEXIT_INIT) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00427) + "Parent: child process %lu exited with status %lu -- Aborting.", + child_pid, exitcode); + shutdown_pending = 1; + } + else { + int i; + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00428) + "Parent: child process %lu exited with status %lu -- Restarting.", + child_pid, exitcode); + for (i = 0; i < ap_threads_per_child; i++) { + ap_update_child_status_from_indexes(0, i, SERVER_DEAD, NULL); + } + } + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + + winnt_note_child_killed(/* slot */ 0); + + if (restart_pending) { + ++my_generation; + ap_scoreboard_image->global->running_generation = my_generation; + } +die_now: + if (shutdown_pending) + { + int timeout = 30000; /* Timeout is milliseconds */ + winnt_mpm_state = AP_MPMQ_STOPPING; + + if (!child_created) { + return 0; /* Tell the caller we do not want to restart */ + } + + /* This shutdown is only marginally graceful. We will give the + * child a bit of time to exit gracefully. If the time expires, + * the child will be wacked. + */ + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_stopping(); + } + /* Signal the child processes to exit */ + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK,APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00429) + "Parent: SetEvent for child process event %pp failed", + event_handles[CHILD_HANDLE]); + } + if (event_handles[CHILD_HANDLE]) { + rv = WaitForSingleObject(event_handles[CHILD_HANDLE], timeout); + if (rv == WAIT_OBJECT_0) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00430) + "Parent: Child process %lu exited successfully.", child_pid); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00431) + "Parent: Forcing termination of child process %lu", + child_pid); + TerminateProcess(event_handles[CHILD_HANDLE], 1); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + } + CloseHandle(child_exit_event); + return 0; /* Tell the caller we do not want to restart */ + } + winnt_mpm_state = AP_MPMQ_STARTING; + CloseHandle(child_exit_event); + return 1; /* Tell the caller we want a restart */ +} + +/* service_nt_main_fn needs to append the StartService() args + * outside of our call stack and thread as the service starts... + */ +apr_array_header_t *mpm_new_argv; + +/* Remember service_to_start failures to log and fail in pre_config. + * Remember inst_argc and inst_argv for installing or starting the + * service after we preflight the config. + */ + +static int winnt_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = MAXIMUM_WAIT_OBJECTS; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = ap_threads_per_child; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = 1; + break; + case AP_MPMQ_MPM_STATE: + *result = winnt_mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static const char *winnt_get_name(void) +{ + return "WinNT"; +} + +#define SERVICE_UNSET (-1) +static apr_status_t service_set = SERVICE_UNSET; +static apr_status_t service_to_start_success; +static int inst_argc; +static const char * const *inst_argv; +static const char *service_name = NULL; + +static void winnt_rewrite_args(process_rec *process) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [transition in service context only] + * -k install + * -k config + * -k uninstall + * -k stop + * -k shutdown (same as -k stop). Maintained for backward compatibility. + * + * We can't leave this phase until we know our identity + * and modify the command arguments appropriately. + * + * We do not care if the .conf file exists or is parsable when + * attempting to stop or uninstall a service. + */ + apr_status_t rv; + char *def_server_root; + char *binpath; + char optbuf[3]; + const char *opt_arg; + int fixed_args; + char *pid; + apr_getopt_t *opt; + int running_as_service = 1; + int errout = 0; + apr_file_t *nullfile; + + pconf = process->pconf; + + osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osver); + + /* We wish this was *always* a reservation, but sadly it wasn't so and + * we couldn't break a hard limit prior to NT Kernel 5.1 + */ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT + && ((osver.dwMajorVersion > 5) + || ((osver.dwMajorVersion == 5) && (osver.dwMinorVersion > 0)))) { + stack_res_flag = STACK_SIZE_PARAM_IS_A_RESERVATION; + } + + /* AP_PARENT_PID is only valid in the child */ + pid = getenv("AP_PARENT_PID"); + if (pid) + { + HANDLE filehand; + HANDLE hproc = GetCurrentProcess(); + DWORD BytesRead; + + /* This is the child */ + my_pid = GetCurrentProcessId(); + parent_pid = (DWORD) atol(pid); + + /* Prevent holding open the (nonexistent) console */ + ap_real_exit_code = 0; + + /* The parent gave us stdin, we need to remember this + * handle, and no longer inherit it at our children + * (we can't slurp it up now, we just aren't ready yet). + * The original handle is closed below, at apr_file_dup2() + */ + pipe = GetStdHandle(STD_INPUT_HANDLE); + if (DuplicateHandle(hproc, pipe, + hproc, &filehand, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + pipe = filehand; + } + + /* The parent gave us stdout of the NUL device, + * and expects us to suck up stdin of all of our + * shared handles and data from the parent. + * Don't infect child processes with our stdin + * handle, use another handle to NUL! + */ + { + apr_file_t *infile, *outfile; + if ((apr_file_open_stdout(&outfile, process->pool) == APR_SUCCESS) + && (apr_file_open_stdin(&infile, process->pool) == APR_SUCCESS)) + apr_file_dup2(infile, outfile, process->pool); + } + + /* This child needs the existing stderr opened for logging, + * already + */ + + /* Read this child's generation number as soon as now, + * so that further hooks can query it. + */ + if (!ReadFile(pipe, &my_generation, sizeof(my_generation), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(my_generation))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), NULL, APLOGNO(02965) + "Child: Unable to retrieve my generation from the parent"); + exit(APEXIT_CHILDINIT); + } + + /* The parent is responsible for providing the + * COMPLETE ARGUMENTS REQUIRED to the child. + * + * No further argument parsing is needed, but + * for good measure we will provide a simple + * signal string for later testing. + */ + signal_arg = "runchild"; + return; + } + + /* This is the parent, we have a long way to go :-) */ + parent_pid = my_pid = GetCurrentProcessId(); + + /* This behavior is voided by setting real_exit_code to 0 */ + atexit(hold_console_open_on_error); + + /* Rewrite process->argv[]; + * + * strip out -k signal into signal_arg + * strip out -n servicename and set the names + * add default -d serverroot from the path of this executable + * + * The end result will look like: + * + * The invocation command (%0) + * The -d serverroot default from the running executable + * The requested service's (-n) registry ConfigArgs + * The WinNT SCM's StartService() args + */ + if ((rv = ap_os_proc_filepath(&binpath, process->pconf)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_CRIT, rv, NULL, APLOGNO(00432) + "Failed to get the full path of %s", process->argv[0]); + exit(APEXIT_INIT); + } + /* WARNING: There is an implicit assumption here that the + * executable resides in ServerRoot or ServerRoot\bin + */ + def_server_root = (char *) apr_filepath_name_get(binpath); + if (def_server_root > binpath) { + *(def_server_root - 1) = '\0'; + def_server_root = (char *) apr_filepath_name_get(binpath); + if (!strcasecmp(def_server_root, "bin")) + *(def_server_root - 1) = '\0'; + } + apr_filepath_merge(&def_server_root, NULL, binpath, + APR_FILEPATH_TRUENAME, process->pool); + + /* Use process->pool so that the rewritten argv + * lasts for the lifetime of the server process, + * because pconf will be destroyed after the + * initial pre-flight of the config parser. + */ + mpm_new_argv = apr_array_make(process->pool, process->argc + 2, + sizeof(const char *)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + *(const char **)apr_array_push(mpm_new_argv) = "-d"; + *(const char **)apr_array_push(mpm_new_argv) = def_server_root; + + fixed_args = mpm_new_argv->nelts; + + optbuf[0] = '-'; + optbuf[2] = '\0'; + apr_getopt_init(&opt, process->pool, process->argc, process->argv); + opt->errfn = NULL; + while ((rv = apr_getopt(opt, "wn:k:" AP_SERVER_BASEARGS, + optbuf + 1, &opt_arg)) == APR_SUCCESS) { + switch (optbuf[1]) { + + /* Shortcuts; include the -w option to hold the window open on error. + * This must not be toggled once we reset ap_real_exit_code to 0! + */ + case 'w': + if (ap_real_exit_code) + ap_real_exit_code = 2; + break; + + case 'n': + service_set = mpm_service_set_name(process->pool, &service_name, + opt_arg); + break; + + case 'k': + signal_arg = opt_arg; + break; + + case 'E': + errout = 1; + /* Fall through so the Apache main() handles the 'E' arg */ + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + + if (opt_arg) { + *(const char **)apr_array_push(mpm_new_argv) = opt_arg; + } + break; + } + } + + /* back up to capture the bad argument */ + if (rv == APR_BADCH || rv == APR_BADARG) { + opt->ind--; + } + + while (opt->ind < opt->argc) { + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, opt->argv[opt->ind++]); + } + + /* Track the number of args actually entered by the user */ + inst_argc = mpm_new_argv->nelts - fixed_args; + + /* Provide a default 'run' -k arg to simplify signal_arg tests */ + if (!signal_arg) + { + signal_arg = "run"; + running_as_service = 0; + } + + if (!strcasecmp(signal_arg, "runservice")) + { + /* Start the NT Service _NOW_ because the WinNT SCM is + * expecting us to rapidly assume control of our own + * process, the SCM will tell us our service name, and + * may have extra StartService() command arguments to + * add for us. + * + * The SCM will generally invoke the executable with + * the c:\win\system32 default directory. This is very + * lethal if folks use ServerRoot /foopath on windows + * without a drive letter. Change to the default root + * (path to apache root, above /bin) for safety. + */ + apr_filepath_set(def_server_root, process->pool); + + /* Any other process has a console, so we don't to begin + * a Win9x service until the configuration is parsed and + * any command line errors are reported. + * + * We hold the return value so that we can die in pre_config + * after logging begins, and the failure can land in the log. + */ + if (!errout) { + mpm_nt_eventlog_stderr_open(service_name, process->pool); + } + service_to_start_success = mpm_service_to_start(&service_name, + process->pool); + if (service_to_start_success == APR_SUCCESS) { + service_set = APR_SUCCESS; + } + + /* Open a null handle to soak stdout in this process. + * Windows service processes are missing any file handle + * usable for stdin/out/err. This was the cause of later + * trouble with invocations of apr_file_open_stdout() + */ + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + process->pool)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, process->pool) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, process->pool); + apr_file_close(nullfile); + } + } + + /* Get the default for any -k option, except run */ + if (service_set == SERVICE_UNSET && strcasecmp(signal_arg, "run")) { + service_set = mpm_service_set_name(process->pool, &service_name, + AP_DEFAULT_SERVICE_NAME); + } + + if (!strcasecmp(signal_arg, "install")) /* -k install */ + { + if (service_set == APR_SUCCESS) + { + ap_log_error(APLOG_MARK,APLOG_ERR, 0, NULL, APLOGNO(00433) + "%s: Service is already installed.", service_name); + exit(APEXIT_INIT); + } + } + else if (running_as_service) + { + if (service_set == APR_SUCCESS) + { + /* Attempt to Uninstall, or stop, before + * we can read the arguments or .conf files + */ + if (!strcasecmp(signal_arg, "uninstall")) { + rv = mpm_service_uninstall(); + exit(rv); + } + + if ((!strcasecmp(signal_arg, "stop")) || + (!strcasecmp(signal_arg, "shutdown"))) { + mpm_signal_service(process->pool, 0); + exit(0); + } + + rv = mpm_merge_service_args(process->pool, mpm_new_argv, + fixed_args); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_INFO, 0, NULL, APLOGNO(00434) + "Using ConfigArgs of the installed service " + "\"%s\".", service_name); + } + else { + ap_log_error(APLOG_MARK,APLOG_WARNING, rv, NULL, APLOGNO(00435) + "No installed ConfigArgs for the service " + "\"%s\", using Apache defaults.", service_name); + } + } + else + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00436) + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + } + if (strcasecmp(signal_arg, "install") && service_set && service_set != SERVICE_UNSET) + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00437) + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + + /* Track the args actually entered by the user. + * These will be used for the -k install parameters, as well as + * for the -k start service override arguments. + */ + inst_argv = (const char * const *)mpm_new_argv->elts + + mpm_new_argv->nelts - inst_argc; + + /* Now, do service install or reconfigure then proceed to + * post_config to test the installed configuration. + */ + if (!strcasecmp(signal_arg, "config")) { /* -k config */ + /* Reconfigure the service */ + rv = mpm_service_install(process->pool, inst_argc, inst_argv, 1); + if (rv != APR_SUCCESS) { + exit(rv); + } + + fprintf(stderr,"Testing httpd.conf....\n"); + fprintf(stderr,"Errors reported here must be corrected before the " + "service can be started.\n"); + } + else if (!strcasecmp(signal_arg, "install")) { /* -k install */ + /* Install the service */ + rv = mpm_service_install(process->pool, inst_argc, inst_argv, 0); + if (rv != APR_SUCCESS) { + exit(rv); + } + + fprintf(stderr,"Testing httpd.conf....\n"); + fprintf(stderr,"Errors reported here must be corrected before the " + "service can be started.\n"); + } + + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *) mpm_new_argv->elts; +} + + +static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *ptemp) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [WinNT errors logged from rewrite_args] + */ + + /* Initialize shared static objects. + * TODO: Put config related statics into an sconf structure. + */ + pconf = pconf_; + + if (ap_exists_config_define("ONE_PROCESS") || + ap_exists_config_define("DEBUG")) + one_process = -1; + + /* XXX: presume proper privileges; one nice thing would be + * a loud emit if running as "LocalSystem"/"SYSTEM" to indicate + * they should change to a user with write access to logs/ alone. + */ + ap_sys_privileges_handlers(1); + + if (!strcasecmp(signal_arg, "runservice") + && (service_to_start_success != APR_SUCCESS)) { + ap_log_error(APLOG_MARK,APLOG_CRIT, service_to_start_success, NULL, APLOGNO(00438) + "%s: Unable to start the service manager.", + service_name); + exit(APEXIT_INIT); + } + else if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_NORMAL + && !one_process && !my_generation) { + /* Open a null handle to soak stdout in this process. + * We need to emulate apr_proc_detach, unix performs this + * same check in the pre_config hook (although it is + * arguably premature). Services already fixed this. + */ + apr_file_t *nullfile; + apr_status_t rv; + apr_pool_t *pproc = apr_pool_parent_get(pconf); + + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + pproc)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, pproc) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, pproc); + apr_file_close(nullfile); + } + } + + ap_listen_pre_config(); + thread_limit = DEFAULT_THREAD_LIMIT; + ap_threads_per_child = DEFAULT_THREADS_PER_CHILD; + + return OK; +} + +static int winnt_check_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec* s) +{ + int is_parent; + int startup = 0; + + /* We want this only in the parent and only the first time around */ + is_parent = (parent_pid == my_pid); + if (is_parent && + ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { + startup = 1; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00439) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of %d threads, decreasing to %d.", + thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00440) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00441) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00442) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* You cannot change ThreadLimit across a restart; ignore + * any such attempts. + */ + if (!first_thread_limit) { + first_thread_limit = thread_limit; + } + else if (thread_limit != first_thread_limit) { + /* Don't need a startup console version here */ + if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00443) + "changing ThreadLimit to %d from original value " + "of %d not allowed during restart", + thread_limit, first_thread_limit); + } + thread_limit = first_thread_limit; + } + + if (ap_threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00444) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of %d threads, decreasing to %d. To increase, please " + "see the ThreadLimit directive.", + ap_threads_per_child, thread_limit, thread_limit); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00445) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + ap_threads_per_child, thread_limit); + } + ap_threads_per_child = thread_limit; + } + else if (ap_threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00446) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", ap_threads_per_child); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00447) + "ThreadsPerChild of %d not allowed, increasing to 1", + ap_threads_per_child); + } + ap_threads_per_child = 1; + } + + return OK; +} + +static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec* s) +{ + apr_status_t rv = 0; + + /* Handle the following SCM aspects in this phase: + * + * -k install (catch and exit as install was handled in rewrite_args) + * -k config (catch and exit as config was handled in rewrite_args) + * -k start + * -k restart + * -k runservice [Win95, only once - after we parsed the config] + * + * because all of these signals are useful _only_ if there + * is a valid conf\httpd.conf environment to start. + * + * We reached this phase by avoiding errors that would cause + * these options to fail unexpectedly in another process. + */ + + if (!strcasecmp(signal_arg, "install")) { + /* Service install happens in the rewrite_args hooks. If we + * made it this far, the server configuration is clean and the + * service will successfully start. + */ + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(0); + } + if (!strcasecmp(signal_arg, "config")) { + /* Service reconfiguration happens in the rewrite_args hooks. If we + * made it this far, the server configuration is clean and the + * service will successfully start. + */ + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(0); + } + + if (!strcasecmp(signal_arg, "start")) { + ap_listen_rec *lr; + + /* Close the listening sockets. */ + for (lr = ap_listeners; lr; lr = lr->next) { + apr_socket_close(lr->sd); + lr->active = 0; + } + rv = mpm_service_start(ptemp, inst_argc, inst_argv); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit (rv); + } + + if (!strcasecmp(signal_arg, "restart")) { + mpm_signal_service(ptemp, 1); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit (rv); + } + + if (parent_pid == my_pid) + { + if (ap_state_query(AP_SQ_MAIN_STATE) != AP_SQ_MS_CREATE_PRE_CONFIG + && ap_state_query(AP_SQ_CONFIG_GEN) == 1) + { + /* This code should be run once in the parent and not run + * across a restart + */ + setup_signal_names(apr_psprintf(pconf, "ap%lu", parent_pid)); + + ap_log_pid(pconf, ap_pid_fname); + + /* Create shutdown event, apPID_shutdown, where PID is the parent + * Apache process ID. Shutdown is signaled by 'apache -k shutdown'. + */ + shutdown_event = CreateEvent(NULL, FALSE, FALSE, signal_shutdown_name); + if (!shutdown_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00448) + "Parent: Cannot create shutdown event %s", signal_shutdown_name); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Create restart event, apPID_restart, where PID is the parent + * Apache process ID. Restart is signaled by 'apache -k restart'. + */ + restart_event = CreateEvent(NULL, FALSE, FALSE, signal_restart_name); + if (!restart_event) { + CloseHandle(shutdown_event); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00449) + "Parent: Cannot create restart event %s", signal_restart_name); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Create the start mutex, as an unnamed object for security. + * The start mutex is used during a restart to prevent more than + * one child process from entering the accept loop at once. + */ + rv = apr_proc_mutex_create(&start_mutex, NULL, + APR_LOCK_DEFAULT, + ap_server_conf->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00450) + "%s: Unable to create the start_mutex.", + service_name); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + /* Always reset our console handler to be the first, even on a restart + * because some modules (e.g. mod_perl) might have set a console + * handler to terminate the process. + */ + if (strcasecmp(signal_arg, "runservice")) + mpm_start_console_handler(); + } + else /* parent_pid != my_pid */ + { + mpm_start_child_console_handler(); + } + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int winnt_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + /* Initialize shared static objects. + */ + if (parent_pid != my_pid) { + return OK; + } + + /* We cannot initialize our listeners if we are restarting + * (the parent process already has glomed on to them) + * nor should we do so for service reconfiguration + * (since the service may already be running.) + */ + if (!strcasecmp(signal_arg, "restart") + || !strcasecmp(signal_arg, "config")) { + return OK; + } + + if (ap_setup_listeners(s) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, + NULL, APLOGNO(00451) "no listening sockets available, shutting down"); + return !OK; + } + + return OK; +} + +static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s) +{ + apr_status_t rv; + + setup_signal_names(apr_psprintf(pchild, "ap%lu", parent_pid)); + + /* This is a child process, not in single process mode */ + if (!one_process) { + /* Set up events and the scoreboard */ + get_handles_from_parent(s, &exit_event, &start_mutex, + &ap_scoreboard_shm); + + /* Set up the listeners */ + get_listeners_from_parent(s); + + /* Done reading from the parent, close that channel */ + CloseHandle(pipe); + } + else { + /* Single process mode - this lock doesn't even need to exist */ + rv = apr_proc_mutex_create(&start_mutex, signal_name_prefix, + APR_LOCK_DEFAULT, s->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00452) + "%s child: Unable to init the start_mutex.", + service_name); + exit(APEXIT_CHILDINIT); + } + + /* Borrow the shutdown_even as our _child_ loop exit event */ + exit_event = shutdown_event; + } +} + + +static int winnt_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +{ + static int restart = 0; /* Default is "not a restart" */ + + /* ### If non-graceful restarts are ever introduced - we need to rerun + * the pre_mpm hook on subsequent non-graceful restarts. But Win32 + * has only graceful style restarts - and we need this hook to act + * the same on Win32 as on Unix. + */ + if (!restart && ((parent_pid == my_pid) || one_process)) { + /* Set up the scoreboard. */ + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + return !OK; + } + } + + if ((parent_pid != my_pid) || one_process) + { + /* The child process or in one_process (debug) mode + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00453) + "Child process is running"); + + child_main(pconf, parent_pid); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00454) + "Child process is exiting"); + return DONE; + } + else + { + /* A real-honest to goodness parent */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00455) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00456) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + + restart = master_main(ap_server_conf, shutdown_event, restart_event); + + if (!restart) + { + /* Shutting down. Clean up... */ + ap_remove_pid(pconf, ap_pid_fname); + apr_proc_mutex_destroy(start_mutex); + + CloseHandle(restart_event); + CloseHandle(shutdown_event); + + return DONE; + } + } + + return OK; /* Restart */ +} + +static void winnt_hooks(apr_pool_t *p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + + ap_hook_pre_config(winnt_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(winnt_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(winnt_post_config, NULL, NULL, 0); + ap_hook_child_init(winnt_child_init, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_open_logs(winnt_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + ap_hook_mpm(winnt_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(winnt_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(winnt_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(mpm_winnt) = { + MPM20_MODULE_STUFF, + winnt_rewrite_args, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + winnt_cmds, /* command apr_table_t */ + winnt_hooks /* register_hooks */ +}; + +#endif /* def WIN32 */ diff --git a/server/mpm/winnt/mpm_winnt.h b/server/mpm/winnt/mpm_winnt.h new file mode 100644 index 0000000..22ba001 --- /dev/null +++ b/server/mpm/winnt/mpm_winnt.h @@ -0,0 +1,96 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file mpm_winnt.h + * @brief WinNT MPM specific + * + * @addtogroup APACHE_MPM_WINNT + * @{ + */ + +#ifndef APACHE_MPM_WINNT_H +#define APACHE_MPM_WINNT_H + +#include "apr_proc_mutex.h" +#include "ap_listen.h" + +/* From service.c: */ + +#define SERVICE_APACHE_RESTART 128 + +#ifndef AP_DEFAULT_SERVICE_NAME +#define AP_DEFAULT_SERVICE_NAME "Apache2.4" +#endif + +#define SERVICECONFIG "System\\CurrentControlSet\\Services\\%s" +#define SERVICEPARAMS "System\\CurrentControlSet\\Services\\%s\\Parameters" + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name); +apr_status_t mpm_merge_service_args(apr_pool_t *p, apr_array_header_t *args, + int fixed_args); + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p); +apr_status_t mpm_service_started(void); +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + char const* const* argv, int reconfig); +apr_status_t mpm_service_uninstall(void); + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + char const* const* argv); + +void mpm_signal_service(apr_pool_t *ptemp, int signal); + +void mpm_service_stopping(void); + +void mpm_start_console_handler(void); +void mpm_start_child_console_handler(void); + +/* From nt_eventlog.c: */ + +void mpm_nt_eventlog_stderr_open(const char *display_name, apr_pool_t *p); +void mpm_nt_eventlog_stderr_flush(void); + +/* From mpm_winnt.c: */ + +extern module AP_MODULE_DECLARE_DATA mpm_winnt_module; +extern int ap_threads_per_child; + +extern DWORD my_pid; +extern apr_proc_mutex_t *start_mutex; +extern HANDLE exit_event; + +extern int winnt_mpm_state; +extern OSVERSIONINFO osver; +extern DWORD stack_res_flag; + +extern void clean_child_exit(int); + +typedef enum { + SIGNAL_PARENT_SHUTDOWN, + SIGNAL_PARENT_RESTART, + SIGNAL_PARENT_RESTART_GRACEFUL +} ap_signal_parent_e; +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type); + +void hold_console_open_on_error(void); + +/* From child.c: */ +void child_main(apr_pool_t *pconf, DWORD parent_pid); + +#endif /* APACHE_MPM_WINNT_H */ +/** @} */ diff --git a/server/mpm/winnt/nt_eventlog.c b/server/mpm/winnt/nt_eventlog.c new file mode 100644 index 0000000..cd49ee6 --- /dev/null +++ b/server/mpm/winnt/nt_eventlog.c @@ -0,0 +1,172 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_log.h" +#include "mpm_winnt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_portable.h" +#include "ap_regkey.h" + +static const char *display_name = NULL; +static HANDLE stderr_thread = NULL; +static HANDLE stderr_ready; + +static DWORD WINAPI service_stderr_thread(LPVOID hPipe) +{ + HANDLE hPipeRead = (HANDLE) hPipe; + HANDLE hEventSource; + char errbuf[256]; + char *errmsg = errbuf; + const char *errarg[9]; + DWORD errres; + ap_regkey_t *regkey; + apr_status_t rv; + apr_pool_t *p; + + apr_pool_create_ex(&p, NULL, NULL, NULL); + apr_pool_tag(p, "service_stderr_thread"); + + errarg[0] = "The Apache service named"; + errarg[1] = display_name; + errarg[2] = "reported the following error:\r\n>>>"; + errarg[3] = errbuf; + errarg[4] = NULL; + errarg[5] = NULL; + errarg[6] = NULL; + errarg[7] = NULL; + errarg[8] = NULL; + + /* What are we going to do in here, bail on the user? not. */ + if ((rv = ap_regkey_open(®key, AP_REGKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\" + "EventLog\\Application\\Apache Service", + APR_READ | APR_WRITE | APR_CREATE, p)) + == APR_SUCCESS) + { + DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | + EVENTLOG_INFORMATION_TYPE; + + /* The stock message file */ + ap_regkey_value_set(regkey, "EventMessageFile", + "%SystemRoot%\\System32\\netmsg.dll", + AP_REGKEY_EXPAND, p); + + ap_regkey_value_raw_set(regkey, "TypesSupported", &dwData, + sizeof(dwData), REG_DWORD, p); + ap_regkey_close(regkey); + } + + hEventSource = RegisterEventSourceW(NULL, L"Apache Service"); + + SetEvent(stderr_ready); + + while (ReadFile(hPipeRead, errmsg, 1, &errres, NULL) && (errres == 1)) + { + if ((errmsg > errbuf) || !apr_isspace(*errmsg)) + { + ++errmsg; + if ((*(errmsg - 1) == '\n') + || (errmsg >= errbuf + sizeof(errbuf) - 1)) + { + while ((errmsg > errbuf) && apr_isspace(*(errmsg - 1))) { + --errmsg; + } + *errmsg = '\0'; + + /* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9' + * The event code in netmsg.dll is 3299 + */ + ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, + 3299, NULL, 9, 0, errarg, NULL); + errmsg = errbuf; + } + } + } + + if ((errres = GetLastError()) != ERROR_BROKEN_PIPE) { + apr_snprintf(errbuf, sizeof(errbuf), + "Win32 error %lu reading stderr pipe stream\r\n", + GetLastError()); + + ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, + 3299, NULL, 9, 0, errarg, NULL); + } + + CloseHandle(hPipeRead); + DeregisterEventSource(hEventSource); + CloseHandle(stderr_thread); + stderr_thread = NULL; + apr_pool_destroy(p); + return 0; +} + + +void mpm_nt_eventlog_stderr_flush(void) +{ + HANDLE cleanup_thread = stderr_thread; + + if (cleanup_thread) { + HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); + fclose(stderr); + CloseHandle(hErr); + WaitForSingleObject(cleanup_thread, 30000); + CloseHandle(cleanup_thread); + } +} + + +void mpm_nt_eventlog_stderr_open(const char *argv0, apr_pool_t *p) +{ + SECURITY_ATTRIBUTES sa; + HANDLE hPipeRead = NULL; + HANDLE hPipeWrite = NULL; + DWORD threadid; + apr_file_t *eventlog_file; + apr_file_t *stderr_file; + + display_name = argv0; + + /* Create a pipe to send stderr messages to the system error log. + * + * _dup2() duplicates the write handle inheritable for us. + */ + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = FALSE; + CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0); + ap_assert(hPipeRead && hPipeWrite); + + stderr_ready = CreateEvent(NULL, FALSE, FALSE, NULL); + stderr_thread = CreateThread(NULL, 65536, service_stderr_thread, + (LPVOID)hPipeRead, stack_res_flag, &threadid); + ap_assert(stderr_ready && stderr_thread); + + WaitForSingleObject(stderr_ready, INFINITE); + + if ((apr_file_open_stderr(&stderr_file, p) + == APR_SUCCESS) + && (apr_os_file_put(&eventlog_file, &hPipeWrite, APR_WRITE, p) + == APR_SUCCESS)) + apr_file_dup2(stderr_file, eventlog_file, p); + + /* The code above _will_ corrupt the StdHandle... + * and we must do so anyways. We set this up only + * after we initialized the posix stderr API. + */ + ap_open_stderr_log(p); +} diff --git a/server/mpm/winnt/service.c b/server/mpm/winnt/service.c new file mode 100644 index 0000000..2e473cf --- /dev/null +++ b/server/mpm/winnt/service.c @@ -0,0 +1,1241 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This module ALONE requires the window message API from user.h + * and the default APR include of windows.h will omit it, so + * preload the API symbols now... + */ + +#define _WINUSER_ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" +#if APR_HAS_UNICODE_FS +#include "arch/win32/apr_arch_utf8.h" +#include "arch/win32/apr_arch_misc.h" +#include <wchar.h> +#endif + +#include "httpd.h" +#include "http_log.h" +#include "mpm_winnt.h" +#include "ap_regkey.h" + +#ifdef NOUSER +#undef NOUSER +#endif +#undef _WINUSER_ +#include <winuser.h> +#include <time.h> + +APLOG_USE_MODULE(mpm_winnt); + +/* Todo; clear up statics */ +static char *mpm_service_name = NULL; +static char *mpm_display_name = NULL; + +#if APR_HAS_UNICODE_FS +static apr_wchar_t *mpm_service_name_w; +#endif + +typedef struct nt_service_ctx_t +{ + HANDLE mpm_thread; /* primary thread handle of the apache server */ + HANDLE service_thread; /* thread service/monitor handle */ + DWORD service_thread_id;/* thread service/monitor ID */ + HANDLE service_init; /* controller thread init mutex */ + HANDLE service_term; /* NT service thread kill signal */ + SERVICE_STATUS ssStatus; + SERVICE_STATUS_HANDLE hServiceStatus; +} nt_service_ctx_t; + +static nt_service_ctx_t globdat; + +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx); + +/* Rather than repeat this logic throughout, create an either-or wide or narrow + * implementation because we don't actually pass strings to OpenSCManager. + * This election is based on build time defines and runtime os version test. + */ +#undef OpenSCManager +typedef SC_HANDLE (WINAPI *fpt_OpenSCManager)(const void *lpMachine, + const void *lpDatabase, + DWORD dwAccess); +static fpt_OpenSCManager pfn_OpenSCManager = NULL; +static APR_INLINE SC_HANDLE OpenSCManager(const void *lpMachine, + const void *lpDatabase, + DWORD dwAccess) +{ + if (!pfn_OpenSCManager) { +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + pfn_OpenSCManager = (fpt_OpenSCManager)OpenSCManagerW; +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + pfn_OpenSCManager = (fpt_OpenSCManager)OpenSCManagerA; +#endif + } + return (*(pfn_OpenSCManager))(lpMachine, lpDatabase, dwAccess); +} + +/* exit() for Win32 is macro mapped (horrible, we agree) that allows us + * to catch the non-zero conditions and inform the console process that + * the application died, and hang on to the console a bit longer. + * + * The macro only maps for http_main.c and other sources that include + * the service.h header, so we best assume it's an error to exit from + * _any_ other module. + * + * If ap_real_exit_code is reset to 0, it will not be set or trigger this + * behavior on exit. All service and child processes are expected to + * reset this flag to zero to avoid undesirable side effects. + */ +AP_DECLARE_DATA int ap_real_exit_code = 1; + +void hold_console_open_on_error(void) +{ + HANDLE hConIn; + HANDLE hConErr; + DWORD result; + time_t start; + time_t remains; + char *msg = "Note the errors or messages above, " + "and press the <ESC> key to exit. "; + CONSOLE_SCREEN_BUFFER_INFO coninfo; + INPUT_RECORD in; + char count[16]; + + if (!ap_real_exit_code) + return; + hConIn = GetStdHandle(STD_INPUT_HANDLE); + hConErr = GetStdHandle(STD_ERROR_HANDLE); + if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE)) + return; + if (!WriteConsole(hConErr, msg, (DWORD)strlen(msg), &result, NULL) + || !result) + return; + if (!GetConsoleScreenBufferInfo(hConErr, &coninfo)) + return; + if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80)) + return; + + start = time(NULL); + do + { + while (PeekConsoleInput(hConIn, &in, 1, &result) && result) + { + if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result) + return; + if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown + && (in.Event.KeyEvent.uChar.AsciiChar == 27)) + return; + if (in.EventType == MOUSE_EVENT + && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)) + return; + } + remains = ((start + 30) - time(NULL)); + sprintf(count, "%d...", + (int)remains); /* 30 or less, so can't overflow int */ + if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition)) + return; + if (!WriteConsole(hConErr, count, (DWORD)strlen(count), &result, NULL) + || !result) + return; + } + while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED); +} + +static BOOL CALLBACK console_control_handler(DWORD ctrl_type) +{ + switch (ctrl_type) + { + case CTRL_BREAK_EVENT: + fprintf(stderr, "Apache server restarting...\n"); + ap_signal_parent(SIGNAL_PARENT_RESTART); + return TRUE; + case CTRL_C_EVENT: + fprintf(stderr, "Apache server interrupted...\n"); + /* for Interrupt signals, shut down the server. + * Tell the system we have dealt with the signal + * without waiting for Apache to terminate. + */ + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + return TRUE; + + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + /* for Terminate signals, shut down the server. + * Wait for Apache to terminate, but respond + * after a reasonable time to tell the system + * that we did attempt to shut ourself down. + */ + fprintf(stderr, "Apache server shutdown initiated...\n"); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + Sleep(30000); + return TRUE; + } + + /* We should never get here, but this is (mostly) harmless */ + return FALSE; +} + + +static void stop_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, FALSE); +} + + +void mpm_start_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, TRUE); + atexit(stop_console_handler); +} + + +void mpm_start_child_console_handler(void) +{ + FreeConsole(); +} + + +/********************************** + WinNT service control management + **********************************/ + +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx) +{ + int rv = APR_SUCCESS; + + if (ctx->hServiceStatus) + { + if (currentState == SERVICE_RUNNING) { + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + ctx->ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP + | SERVICE_ACCEPT_SHUTDOWN; + } + else if (currentState == SERVICE_STOPPED) { + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + /* An unexpected exit? Better to error! */ + if (ctx->ssStatus.dwCurrentState != SERVICE_STOP_PENDING + && !ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwServiceSpecificExitCode = 1; + if (ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + } + else { + ++ctx->ssStatus.dwCheckPoint; + ctx->ssStatus.dwControlsAccepted = 0; + if(waitHint) + ctx->ssStatus.dwWaitHint = waitHint; + } + + ctx->ssStatus.dwCurrentState = currentState; + + rv = SetServiceStatus(ctx->hServiceStatus, &ctx->ssStatus); + } + return(rv); +} + +/* Note this works on Win2000 and later due to ChangeServiceConfig2 + * Continue to test its existence, but at least drop the feature + * of revising service description tags prior to Win2000. + */ + +/* borrowed from mpm_winnt.c */ +extern apr_pool_t *pconf; + +static void set_service_description(void) +{ + const char *full_description; + SC_HANDLE schSCManager; + + /* Nothing to do if we are a console + */ + if (!mpm_service_name) + return; + + /* Time to fix up the description, upon each successful restart + */ + full_description = ap_get_server_description(); + + if ((ChangeServiceConfig2) && + (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT))) + { + SC_HANDLE schService; + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, + (LPCWSTR)mpm_service_name_w, + SERVICE_CHANGE_CONFIG); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + } +#endif + if (schService) { + /* Cast is necessary, ChangeServiceConfig2 handles multiple + * object types, some volatile, some not. + */ +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(full_description) + 1; + apr_size_t wslen = slen; + apr_wchar_t *full_description_w = + (apr_wchar_t*)apr_palloc(pconf, + wslen * sizeof(apr_wchar_t)); + apr_status_t rv = apr_conv_utf8_to_ucs2(full_description, &slen, + full_description_w, + &wslen); + if ((rv != APR_SUCCESS) || slen + || ChangeServiceConfig2W(schService, 1 + /*SERVICE_CONFIG_DESCRIPTION*/, + (LPVOID) &full_description_w)) + full_description = NULL; + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + if (ChangeServiceConfig2(schService, + 1 /* SERVICE_CONFIG_DESCRIPTION */, + (LPVOID) &full_description)) + full_description = NULL; + } +#endif + CloseServiceHandle(schService); + } + CloseServiceHandle(schSCManager); + } +} + +/* handle the SCM's ControlService() callbacks to our service */ + +static DWORD WINAPI service_nt_ctrl(DWORD dwCtrlCode, DWORD dwEventType, + LPVOID lpEventData, LPVOID lpContext) +{ + nt_service_ctx_t *ctx = lpContext; + + /* SHUTDOWN is offered before STOP, accept the first opportunity */ + if ((dwCtrlCode == SERVICE_CONTROL_STOP) + || (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN)) + { + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, ctx); + return (NO_ERROR); + } + if (dwCtrlCode == SERVICE_APACHE_RESTART) + { + ap_signal_parent(SIGNAL_PARENT_RESTART); + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + return (NO_ERROR); + } + if (dwCtrlCode == SERVICE_CONTROL_INTERROGATE) { + ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, 0, ctx); + return (NO_ERROR); + } + + return (ERROR_CALL_NOT_IMPLEMENTED); +} + + +/* service_nt_main_fn is outside of the call stack and outside of the + * primary server thread... so now we _really_ need a placeholder! + * The winnt_rewrite_args has created and shared mpm_new_argv with us. + */ +extern apr_array_header_t *mpm_new_argv; + +#if APR_HAS_UNICODE_FS +static void __stdcall service_nt_main_fn_w(DWORD argc, LPWSTR *argv) +{ + const char *ignored; + nt_service_ctx_t *ctx = &globdat; + char *service_name; + apr_size_t wslen = wcslen(argv[0]) + 1; + apr_size_t slen = wslen * 3 - 2; + + service_name = malloc(slen); + (void)apr_conv_ucs2_to_utf8(argv[0], &wslen, service_name, &slen); + + /* args and service names live in the same pool */ + mpm_service_set_name(mpm_new_argv->pool, &ignored, service_name); + + memset(&ctx->ssStatus, 0, sizeof(ctx->ssStatus)); + ctx->ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ctx->ssStatus.dwCurrentState = SERVICE_START_PENDING; + ctx->ssStatus.dwCheckPoint = 1; + if (!(ctx->hServiceStatus = + RegisterServiceCtrlHandlerExW(argv[0], service_nt_ctrl, ctx))) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(00365) "Failure registering service handler"); + return; + } + + /* Report status, no errors, and buy 3 more seconds */ + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + + /* We need to append all the command arguments passed via StartService() + * to our running service... which just got here via the SCM... + * but we have no interest in argv[0] for the mpm_new_argv list. + */ + if (argc > 1) + { + char **cmb_data, **cmb; + DWORD i; + + mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1; + cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *)); + + /* mpm_new_argv remains first (of lower significance) */ + memcpy (cmb_data, mpm_new_argv->elts, + mpm_new_argv->elt_size * mpm_new_argv->nelts); + + /* Service args follow from StartService() invocation */ + memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, + mpm_new_argv->elt_size * (argc - 1)); + + cmb = cmb_data + mpm_new_argv->nelts; + + for (i = 1; i < argc; ++i) + { + wslen = wcslen(argv[i]) + 1; + slen = wslen * 3 - 2; + service_name = malloc(slen); + (void)apr_conv_ucs2_to_utf8(argv[i], &wslen, *(cmb++), &slen); + } + + /* The replacement arg list is complete */ + mpm_new_argv->elts = (char *)cmb_data; + mpm_new_argv->nelts = mpm_new_argv->nalloc; + } + + /* Let the main thread continue now... but hang on to the + * signal_monitor event so we can take further action + */ + SetEvent(ctx->service_init); + + WaitForSingleObject(ctx->service_term, INFINITE); +} +#endif /* APR_HAS_UNICODE_FS */ + + +#if APR_HAS_ANSI_FS +static void __stdcall service_nt_main_fn(DWORD argc, LPSTR *argv) +{ + const char *ignored; + nt_service_ctx_t *ctx = &globdat; + + /* args and service names live in the same pool */ + mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]); + + memset(&ctx->ssStatus, 0, sizeof(ctx->ssStatus)); + ctx->ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ctx->ssStatus.dwCurrentState = SERVICE_START_PENDING; + ctx->ssStatus.dwCheckPoint = 1; + + if (!(ctx->hServiceStatus = + RegisterServiceCtrlHandlerExA(argv[0], service_nt_ctrl, ctx))) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10008) "Failure registering service handler"); + return; + } + + /* Report status, no errors, and buy 3 more seconds */ + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + + /* We need to append all the command arguments passed via StartService() + * to our running service... which just got here via the SCM... + * but we have no interest in argv[0] for the mpm_new_argv list. + */ + if (argc > 1) + { + char **cmb_data; + + mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1; + cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *)); + + /* mpm_new_argv remains first (of lower significance) */ + memcpy (cmb_data, mpm_new_argv->elts, + mpm_new_argv->elt_size * mpm_new_argv->nelts); + + /* Service args follow from StartService() invocation */ + memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, + mpm_new_argv->elt_size * (argc - 1)); + + /* The replacement arg list is complete */ + mpm_new_argv->elts = (char *)cmb_data; + mpm_new_argv->nelts = mpm_new_argv->nalloc; + } + + /* Let the main thread continue now... but hang on to the + * signal_monitor event so we can take further action + */ + SetEvent(ctx->service_init); + + WaitForSingleObject(ctx->service_term, INFINITE); +} +#endif + + + static DWORD WINAPI service_nt_dispatch_thread(LPVOID nada) + { +#if APR_HAS_UNICODE_FS + SERVICE_TABLE_ENTRYW dispatchTable_w[] = + { + { L"", service_nt_main_fn_w }, + { NULL, NULL } + }; +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + SERVICE_TABLE_ENTRYA dispatchTable[] = + { + { "", service_nt_main_fn }, + { NULL, NULL } + }; +#endif + apr_status_t rv; + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + rv = StartServiceCtrlDispatcherW(dispatchTable_w); +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + rv = StartServiceCtrlDispatcherA(dispatchTable); +#endif + if (rv) { + rv = APR_SUCCESS; + } + else { + /* This is a genuine failure of the SCM. */ + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00366) "Error starting Windows service control " + "dispatcher"); + } + return (rv); +} + + +/* The service configuration's is stored under the following trees: + * + * HKLM\System\CurrentControlSet\Services\[service name] + * + * \DisplayName + * \ImagePath + * \Parameters\ConfigArgs + */ + + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name) +{ + char key_name[MAX_PATH]; + ap_regkey_t *key; + apr_status_t rv; + + /* ### Needs improvement, on Win2K the user can _easily_ + * change the display name to a string that doesn't reflect + * the internal service name + whitespace! + */ + mpm_service_name = apr_palloc(p, strlen(set_name) + 1); + apr_collapse_spaces((char*) mpm_service_name, set_name); +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(mpm_service_name) + 1; + apr_size_t wslen = slen; + mpm_service_name_w = apr_palloc(p, wslen * sizeof(apr_wchar_t)); + rv = apr_conv_utf8_to_ucs2(mpm_service_name, &slen, + mpm_service_name_w, &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + } +#endif /* APR_HAS_UNICODE_FS */ + + apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + /* Take the given literal name if there is no service entry */ + mpm_display_name = apr_pstrdup(p, set_name); + } + *display_name = mpm_display_name; + + return rv; +} + + +apr_status_t mpm_merge_service_args(apr_pool_t *p, + apr_array_header_t *args, + int fixed_args) +{ + apr_array_header_t *svc_args = NULL; + char conf_key[MAX_PATH]; + char **cmb_data; + apr_status_t rv; + ap_regkey_t *key; + + apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + if (rv == ERROR_FILE_NOT_FOUND) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00367) + "No ConfigArgs registered for the '%s' service, " + "perhaps this service is not installed?", + mpm_service_name); + return APR_SUCCESS; + } + else + return (rv); + } + + if (!svc_args || svc_args->nelts == 0) { + return (APR_SUCCESS); + } + + /* Now we have the mpm_service_name arg, and the mpm_runservice_nt() + * call appended the arguments passed by StartService(), so it's + * time to _prepend_ the default arguments for the server from + * the service's default arguments (all others override them)... + */ + args->nalloc = args->nelts + svc_args->nelts; + cmb_data = malloc(args->nalloc * sizeof(const char *)); + + /* First three args (argv[0], -f, path) remain first */ + memcpy(cmb_data, args->elts, args->elt_size * fixed_args); + + /* Service args follow from service registry array */ + memcpy(cmb_data + fixed_args, svc_args->elts, + svc_args->elt_size * svc_args->nelts); + + /* Remaining new args follow */ + memcpy(cmb_data + fixed_args + svc_args->nelts, + (const char **)args->elts + fixed_args, + args->elt_size * (args->nelts - fixed_args)); + + args->elts = (char *)cmb_data; + args->nelts = args->nalloc; + + return APR_SUCCESS; +} + + +static void service_stopped(void) +{ + /* Still have a thread & window to clean up, so signal now */ + if (globdat.service_thread) + { + /* Stop logging to the event log */ + mpm_nt_eventlog_stderr_flush(); + + /* Cause the service_nt_main_fn to complete */ + ReleaseMutex(globdat.service_term); + + ReportStatusToSCMgr(SERVICE_STOPPED, 0, &globdat); + + WaitForSingleObject(globdat.service_thread, 5000); + CloseHandle(globdat.service_thread); + } +} + + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p) +{ + HANDLE hProc = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + HANDLE waitfor[2]; + + /* Prevent holding open the (hidden) console */ + ap_real_exit_code = 0; + + /* GetCurrentThread returns a psuedo-handle, we need + * a real handle for another thread to wait upon. + */ + if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread), + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + return APR_ENOTHREAD; + } + + globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL); + globdat.service_term = CreateMutex(NULL, TRUE, NULL); + if (!globdat.service_init || !globdat.service_term) { + return APR_EGENERAL; + } + + globdat.service_thread = CreateThread(NULL, 65536, + service_nt_dispatch_thread, + NULL, stack_res_flag, + &globdat.service_thread_id); + + if (!globdat.service_thread) { + return APR_ENOTHREAD; + } + + waitfor[0] = globdat.service_init; + waitfor[1] = globdat.service_thread; + + /* Wait for controlling thread init or termination */ + if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) { + return APR_ENOTHREAD; + } + + atexit(service_stopped); + *display_name = mpm_display_name; + return APR_SUCCESS; +} + + +apr_status_t mpm_service_started(void) +{ + set_service_description(); + ReportStatusToSCMgr(SERVICE_RUNNING, 0, &globdat); + return APR_SUCCESS; +} + + +void mpm_service_stopping(void) +{ + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, &globdat); +} + + +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + const char * const * argv, int reconfig) +{ + char key_name[MAX_PATH]; + char *launch_cmd; + ap_regkey_t *key; + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + DWORD rc; +#if APR_HAS_UNICODE_FS + apr_wchar_t *display_name_w; + apr_wchar_t *launch_cmd_w; +#endif + + fprintf(stderr, reconfig ? "Reconfiguring the '%s' service\n" + : "Installing the '%s' service\n", + mpm_display_name); + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(mpm_display_name) + 1; + apr_size_t wslen = slen; + display_name_w = apr_palloc(ptemp, wslen * sizeof(apr_wchar_t)); + rv = apr_conv_utf8_to_ucs2(mpm_display_name, &slen, + display_name_w, &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + + launch_cmd_w = apr_palloc(ptemp, (MAX_PATH + 17) * sizeof(apr_wchar_t)); + launch_cmd_w[0] = L'"'; + rc = GetModuleFileNameW(NULL, launch_cmd_w + 1, MAX_PATH); + wcscpy(launch_cmd_w + rc + 1, L"\" -k runservice"); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + launch_cmd = apr_palloc(ptemp, MAX_PATH + 17); + launch_cmd[0] = '"'; + rc = GetModuleFileName(NULL, launch_cmd + 1, MAX_PATH); + strcpy(launch_cmd + rc + 1, "\" -k runservice"); + } +#endif + if (rc == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00368) "GetModuleFileName failed"); + return rv; + } + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CREATE_SERVICE); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00369) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Administrator?"); + return (rv); + } + + if (reconfig) { +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_CHANGE_CONFIG); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + CloseServiceHandle(schSCManager); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00373) "Failed to open the '%s' service", + mpm_display_name); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + rc = ChangeServiceConfigW(schService, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + launch_cmd_w, NULL, NULL, + L"Tcpip\0Afd\0", NULL, NULL, + display_name_w); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + rc = ChangeServiceConfig(schService, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + launch_cmd, NULL, NULL, + "Tcpip\0Afd\0", NULL, NULL, + mpm_display_name); + } +#endif + if (!rc) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(02652) "ChangeServiceConfig failed"); + + /* !schService aborts configuration below */ + CloseServiceHandle(schService); + schService = NULL; + } + } + else { + /* RPCSS is the Remote Procedure Call (RPC) Locator required + * for DCOM communication pipes. I am far from convinced we + * should add this to the default service dependencies, but + * be warned that future apache modules or ISAPI dll's may + * depend on it. + */ +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = CreateServiceW(schSCManager, // SCManager database + mpm_service_name_w, // name of service + display_name_w, // name to display + SERVICE_ALL_ACCESS, // access required + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + launch_cmd_w, // service's binary + NULL, // no load svc group + NULL, // no tag identifier + L"Tcpip\0Afd\0", // dependencies + NULL, // use SYSTEM account + NULL); // no password + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = CreateService(schSCManager, // SCManager database + mpm_service_name, // name of service + mpm_display_name, // name to display + SERVICE_ALL_ACCESS, // access required + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + launch_cmd, // service's binary + NULL, // no load svc group + NULL, // no tag identifier + "Tcpip\0Afd\0", // dependencies + NULL, // use SYSTEM account + NULL); // no password + } +#endif + if (!schService) + { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00370) "Failed to create the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return (rv); + } + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + set_service_description(); + + /* Store the service ConfigArgs in the registry... + */ + apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ | APR_WRITE | APR_CREATE, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00371) "Failed to store ConfigArgs for the " + "'%s' service in the registry.", mpm_display_name); + return (rv); + } + fprintf(stderr, "The '%s' service is successfully installed.\n", + mpm_display_name); + return APR_SUCCESS; +} + + +apr_status_t mpm_service_uninstall(void) +{ + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + fprintf(stderr, "Removing the '%s' service\n", mpm_display_name); + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10009) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Administrator?"); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, DELETE); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, DELETE); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10010) "Failed to open the '%s' service", + mpm_display_name); + return (rv); + } + + /* assure the service is stopped before continuing + * + * This may be out of order... we might not be able to be + * granted all access if the service is running anyway. + * + * And do we want to make it *this easy* for them + * to uninstall their service unintentionally? + */ + /* ap_stop_service(schService); + */ + + if (DeleteService(schService) == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00374) "Failed to delete the '%s' service", + mpm_display_name); + return (rv); + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + fprintf(stderr, "The '%s' service has been removed successfully.\n", + mpm_display_name); + return APR_SUCCESS; +} + + +/* signal_service_transition is a simple thunk to signal the service + * and monitor its successful transition. If the signal passed is 0, + * then the caller is assumed to already have performed some service + * operation to be monitored (such as StartService), and no actual + * ControlService signal is sent. + */ + +static int signal_service_transition(SC_HANDLE schService, DWORD signal, + DWORD pending, DWORD complete) +{ + if (signal && !ControlService(schService, signal, &globdat.ssStatus)) + return FALSE; + + do { + Sleep(1000); + if (!QueryServiceStatus(schService, &globdat.ssStatus)) + return FALSE; + } while (globdat.ssStatus.dwCurrentState == pending); + + return (globdat.ssStatus.dwCurrentState == complete); +} + + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + const char * const * argv) +{ + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + fprintf(stderr, "Starting the '%s' service\n", mpm_display_name); + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10011) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Administrator?"); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_START | SERVICE_QUERY_STATUS); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_START | SERVICE_QUERY_STATUS); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10012) "Failed to open the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return (rv); + } + + if (QueryServiceStatus(schService, &globdat.ssStatus) + && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, + APLOGNO(00377) "The '%s' service is already started!", + mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 0; + } + + rv = APR_EINIT; +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + LPWSTR *start_argv_w = malloc((argc + 1) * sizeof(LPCWSTR)); + int i; + + for (i = 0; i < argc; ++i) + { + apr_size_t slen = strlen(argv[i]) + 1; + apr_size_t wslen = slen; + start_argv_w[i] = malloc(wslen * sizeof(WCHAR)); + rv = apr_conv_utf8_to_ucs2(argv[i], &slen, start_argv_w[i], &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + } + start_argv_w[argc] = NULL; + + if (StartServiceW(schService, argc, start_argv_w) + && signal_service_transition(schService, 0, /* test only */ + SERVICE_START_PENDING, + SERVICE_RUNNING)) + rv = APR_SUCCESS; + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + char **start_argv = malloc((argc + 1) * sizeof(const char *)); + memcpy(start_argv, argv, argc * sizeof(const char *)); + start_argv[argc] = NULL; + + if (StartService(schService, argc, start_argv) + && signal_service_transition(schService, 0, /* test only */ + SERVICE_START_PENDING, + SERVICE_RUNNING)) + rv = APR_SUCCESS; + } +#endif + if (rv != APR_SUCCESS) + rv = apr_get_os_error(); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + if (rv == APR_SUCCESS) + fprintf(stderr, "The '%s' service is running.\n", mpm_display_name); + else + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00378) + "Failed to start the '%s' service", + mpm_display_name); + + return rv; +} + + +/* signal is zero to stop, non-zero for restart */ + +void mpm_signal_service(apr_pool_t *ptemp, int signal) +{ + int success = FALSE; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + schSCManager = OpenSCManager(NULL, NULL, /* default machine & database */ + SC_MANAGER_CONNECT); + + if (!schSCManager) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10013) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Administrator?"); + return; + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_INTERROGATE | SERVICE_QUERY_STATUS | + SERVICE_USER_DEFINED_CONTROL | + SERVICE_START | SERVICE_STOP); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_INTERROGATE | SERVICE_QUERY_STATUS | + SERVICE_USER_DEFINED_CONTROL | + SERVICE_START | SERVICE_STOP); + } +#endif + if (schService == NULL) { + /* Could not open the service */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10014) "Failed to open the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return; + } + + if (!QueryServiceStatus(schService, &globdat.ssStatus)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(00381) "Query of the '%s' service failed", + mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) { + fprintf(stderr, "The '%s' service is not started.\n", mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + fprintf(stderr, signal ? "The '%s' service is restarting.\n" + : "The '%s' service is stopping.\n", + mpm_display_name); + + if (!signal) + success = signal_service_transition(schService, + SERVICE_CONTROL_STOP, + SERVICE_STOP_PENDING, + SERVICE_STOPPED); + else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) { + mpm_service_start(ptemp, 0, NULL); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + else + success = signal_service_transition(schService, + SERVICE_APACHE_RESTART, + SERVICE_START_PENDING, + SERVICE_RUNNING); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + if (success) + fprintf(stderr, signal ? "The '%s' service has restarted.\n" + : "The '%s' service has stopped.\n", + mpm_display_name); + else + fprintf(stderr, signal ? "Failed to restart the '%s' service.\n" + : "Failed to stop the '%s' service.\n", + mpm_display_name); +} diff --git a/server/mpm/worker/Makefile.in b/server/mpm/worker/Makefile.in new file mode 100644 index 0000000..e32210f --- /dev/null +++ b/server/mpm/worker/Makefile.in @@ -0,0 +1,2 @@ + +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/worker/config.m4 b/server/mpm/worker/config.m4 new file mode 100644 index 0000000..1a50026 --- /dev/null +++ b/server/mpm/worker/config.m4 @@ -0,0 +1,11 @@ +AC_MSG_CHECKING(if worker MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +elif test $ac_cv_define_APR_HAS_THREADS != yes; then + AC_MSG_RESULT(no - APR does not support threads) +elif test $have_threaded_sig_graceful != yes; then + AC_MSG_RESULT(no - SIG_GRACEFUL cannot be used with a threaded MPM) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(worker, yes, yes) +fi diff --git a/server/mpm/worker/config3.m4 b/server/mpm/worker/config3.m4 new file mode 100644 index 0000000..6c1eb17 --- /dev/null +++ b/server/mpm/worker/config3.m4 @@ -0,0 +1,5 @@ +dnl ## XXX - Need a more thorough check of the proper flags to use + +APACHE_MPM_MODULE(worker, $enable_mpm_worker, worker.lo,[ + AC_CHECK_FUNCS(pthread_kill) +]) diff --git a/server/mpm/worker/mpm_default.h b/server/mpm/worker/mpm_default.h new file mode 100644 index 0000000..464250e --- /dev/null +++ b/server/mpm/worker/mpm_default.h @@ -0,0 +1,55 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file worker/mpm_default.h + * @brief Worker MPM defaults + * + * @defgroup APACHE_MPM_WORKER Worker MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers to spawn off by default --- also, if fewer than + * this free when the caretaker checks, it will spawn more. + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 3 +#endif + +/* Maximum number of *free* server processes --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_FREE_DAEMON +#define DEFAULT_MAX_FREE_DAEMON 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_FREE_DAEMON +#define DEFAULT_MIN_FREE_DAEMON 3 +#endif + +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 25 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c new file mode 100644 index 0000000..7b572bd --- /dev/null +++ b/server/mpm/worker/worker.c @@ -0,0 +1,2455 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* The purpose of this MPM is to fix the design flaws in the threaded + * model. Because of the way that pthreads and mutex locks interact, + * it is basically impossible to cleanly gracefully shutdown a child + * process if multiple threads are all blocked in accept. This model + * fixes those problems. + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_strings.h" +#include "apr_file_io.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_thread_mutex.h" +#include "apr_proc_mutex.h" +#include "apr_poll.h" + +#include <stdlib.h> + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if APR_HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifdef HAVE_SYS_PROCESSOR_H +#include <sys/processor.h> /* for bindprocessor() */ +#endif + +#if !APR_HAS_THREADS +#error The Worker MPM requires APR threads, but they are unavailable. +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "ap_mpm.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "mpm_fdqueue.h" +#include "mpm_default.h" +#include "util_mutex.h" +#include "unixd.h" + +#include <signal.h> +#include <limits.h> /* for INT_MAX */ + +/* Limit on the total --- clients will be locked out if more servers than + * this are needed. It is intended solely to keep the server from crashing + * when things get out of hand. + * + * We keep a hard maximum number of servers, for two reasons --- first off, + * in case something goes seriously wrong, we want to stop the fork bomb + * short of actually crashing the machine we're running on by filling some + * kernel table. Secondly, it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_SERVER_LIMIT +#define DEFAULT_SERVER_LIMIT 16 +#endif + +/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_SERVER_LIMIT +#define MAX_SERVER_LIMIT 20000 +#endif + +/* Limit on the threads per process. Clients will be locked out if more than + * this * server_limit are needed. + * + * We keep this for one reason it keeps the size of the scoreboard file small + * enough that we can read the whole thing without worrying too much about + * the overhead. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 64 +#endif + +/* Admin can't tune ThreadLimit beyond MAX_THREAD_LIMIT. We want + * some sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 20000 +#endif + +/* + * Actual definitions of config globals + */ + +static int threads_per_child = 0; /* Worker threads per child */ +static int ap_daemons_to_start = 0; +static int min_spare_threads = 0; +static int max_spare_threads = 0; +static int ap_daemons_limit = 0; +static int max_workers = 0; +static int server_limit = 0; +static int thread_limit = 0; +static int had_healthy_child = 0; +static int dying = 0; +static int workers_may_exit = 0; +static int start_thread_may_exit = 0; +static int listener_may_exit = 0; +static int requests_this_child; +static int num_listensocks = 0; +static int resource_shortage = 0; +static fd_queue_t *worker_queue; +static fd_queue_info_t *worker_queue_info; +static apr_pollset_t *worker_pollset; + + +/* data retained by worker across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook + */ +typedef struct worker_retained_data { + ap_unixd_mpm_retained_data *mpm; + + int first_server_limit; + int first_thread_limit; + int sick_child_detected; + int maxclients_reported; + int near_maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers 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 + * maintained per listeners bucket, 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; + +typedef struct worker_child_bucket { + ap_pod_t *pod; + ap_listen_rec *listeners; + apr_proc_mutex_t *mutex; +} worker_child_bucket; +static worker_child_bucket *all_buckets, /* All listeners buckets */ + *my_bucket; /* Current child bucket */ + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) + +/* The structure used to pass unique initialization info to each thread */ +typedef struct { + int pid; + int tid; + int sd; +} proc_info; + +/* Structure used to pass information to the thread responsible for + * creating the rest of the threads. + */ +typedef struct { + apr_thread_t **threads; + apr_thread_t *listener; + int child_num_arg; + apr_threadattr_t *threadattr; +} thread_starter; + +#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t) + +/* The worker MPM respects a couple of runtime flags that can aid + * in debugging. Setting the -DNO_DETACH flag will prevent the root process + * from detaching from its controlling terminal. Additionally, setting + * the -DONE_PROCESS flag (which implies -DNO_DETACH) will get you the + * child_main loop running in the process which originally started up. + * This gives you a pretty nice debugging environment. (You'll get a SIGHUP + * early in standalone_main; just continue through. This is the server + * trying to kill off any child processes which it might have lying + * around --- Apache doesn't keep track of their pids, it just sends + * SIGHUP to the process group, ignoring it in the root process. + * Continue through and you'll be fine.). + */ + +static int one_process = 0; + +#ifdef DEBUG_SIGSTOP +int raise_sigstop_flags; +#endif + +static apr_pool_t *pconf; /* Pool for config stuff */ +static apr_pool_t *pchild; /* Pool for httpd child stuff */ +static apr_pool_t *pruntime; /* Pool for MPM threads stuff */ + +static pid_t ap_my_pid; /* Linux getpid() doesn't work except in main + thread. Use this instead */ +static pid_t parent_pid; +static apr_os_thread_t *listener_os_thread; + +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT +#define SAFE_ACCEPT(stmt) (ap_listeners->next ? (stmt) : APR_SUCCESS) +#else +#define SAFE_ACCEPT(stmt) (stmt) +#endif + +/* The LISTENER_SIGNAL signal will be sent from the main thread to the + * listener thread to wake it up for graceful termination (what a child + * process from an old generation does when the admin does "apachectl + * graceful"). This signal will be blocked in all threads of a child + * process except for the listener thread. + */ +#define LISTENER_SIGNAL SIGHUP + +/* The WORKER_SIGNAL signal will be sent from the main thread to the + * worker threads during an ungraceful restart or shutdown. + * This ensures that on systems (i.e., Linux) where closing the worker + * socket doesn't awake the worker thread when it is polling on the socket + * (especially in apr_wait_for_io_or_timeout() when handling + * Keep-Alive connections), close_worker_sockets() and join_workers() + * still function in timely manner and allow ungraceful shutdowns to + * proceed to completion. Otherwise join_workers() doesn't return + * before the main process decides the child process is non-responsive + * and sends a SIGKILL. + */ +#define WORKER_SIGNAL AP_SIG_GRACEFUL + +/* An array of socket descriptors in use by each thread used to + * perform a non-graceful (forced) shutdown of the server. */ +static apr_socket_t **worker_sockets; + +static void close_worker_sockets(void) +{ + int i; + for (i = 0; i < threads_per_child; i++) { + if (worker_sockets[i]) { + apr_socket_close(worker_sockets[i]); + worker_sockets[i] = NULL; + } + } +} + +static void wakeup_listener(void) +{ + listener_may_exit = 1; + if (!listener_os_thread) { + /* XXX there is an obscure path that this doesn't handle perfectly: + * right after listener thread is created but before + * listener_os_thread is set, the first worker thread hits an + * error and starts graceful termination + */ + return; + } + + /* unblock the listener if it's waiting for a worker */ + ap_queue_info_term(worker_queue_info); + + /* + * we should just be able to "kill(ap_my_pid, LISTENER_SIGNAL)" on all + * platforms and wake up the listener thread since it is the only thread + * with SIGHUP unblocked, but that doesn't work on Linux + */ +#ifdef HAVE_PTHREAD_KILL + pthread_kill(*listener_os_thread, LISTENER_SIGNAL); +#else + kill(ap_my_pid, LISTENER_SIGNAL); +#endif +} + +#define ST_INIT 0 +#define ST_GRACEFUL 1 +#define ST_UNGRACEFUL 2 + +static int terminate_mode = ST_INIT; + +static void signal_threads(int mode) +{ + if (terminate_mode == mode) { + return; + } + terminate_mode = mode; + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + /* in case we weren't called from the listener thread, wake up the + * listener thread + */ + wakeup_listener(); + + /* for ungraceful termination, let the workers exit now; + * for graceful termination, the listener thread will notify the + * workers to exit once it has stopped accepting new connections + */ + if (mode == ST_UNGRACEFUL) { + workers_may_exit = 1; + ap_queue_interrupt_all(worker_queue); + close_worker_sockets(); /* forcefully kill all current connections */ + } + + ap_run_child_stopping(pchild, mode == ST_GRACEFUL); +} + +static int worker_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = retained->max_daemons_limit; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = server_limit; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = threads_per_child; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = min_spare_threads; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = max_spare_threads; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = ap_daemons_limit; + break; + case AP_MPMQ_MPM_STATE: + *result = retained->mpm->mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = retained->mpm->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static void worker_note_child_killed(int childnum, pid_t pid, ap_generation_t gen) +{ + 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_generation_t gen = retained->mpm->my_generation; + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = gen; + ap_run_child_status(ap_server_conf, pid, gen, 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, APLOGNO(00263) + "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) +{ + return "worker"; +} + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + if (terminate_mode == ST_INIT) { + ap_run_child_stopping(pchild, 0); + } + + if (pchild) { + apr_pool_destroy(pchild); + } + + if (one_process) { + worker_note_child_killed(/* slot */ 0, 0, 0); + } + + exit(code); +} + +static void just_die(int sig) +{ + clean_child_exit(0); +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static int child_fatal; + +/***************************************************************** + * Here follows a long bunch of generic server bookkeeping stuff... + */ + +/***************************************************************** + * Child process main loop. + */ + +static void process_socket(apr_thread_t *thd, apr_pool_t *p, apr_socket_t *sock, + int my_child_num, + int my_thread_num, apr_bucket_alloc_t *bucket_alloc) +{ + conn_rec *current_conn; + long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num); + ap_sb_handle_t *sbh; + + ap_create_sb_handle(&sbh, p, my_child_num, my_thread_num); + + current_conn = ap_run_create_connection(p, ap_server_conf, sock, + conn_id, sbh, bucket_alloc); + if (current_conn) { + current_conn->current_thread = thd; + ap_process_connection(current_conn, sock); + ap_lingering_close(current_conn); + } +} + +/* requests_this_child has gone to zero or below. See if the admin coded + "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) +{ + if (ap_max_requests_per_child) { + signal_threads(ST_GRACEFUL); + } + else { + requests_this_child = INT_MAX; /* keep going */ + } +} + +static void unblock_signal(int sig) +{ + sigset_t sig_mask; + + sigemptyset(&sig_mask); + sigaddset(&sig_mask, sig); +#if defined(SIGPROCMASK_SETS_THREAD_MASK) + sigprocmask(SIG_UNBLOCK, &sig_mask, NULL); +#else + pthread_sigmask(SIG_UNBLOCK, &sig_mask, NULL); +#endif +} + +static void dummy_signal_handler(int sig) +{ + /* XXX If specifying SIG_IGN is guaranteed to unblock a syscall, + * then we don't need this goofy function. + */ +} + +static void accept_mutex_error(const char *func, apr_status_t rv, int process_slot) +{ + int level = APLOG_EMERG; + + if (ap_scoreboard_image->parent[process_slot].generation != + ap_scoreboard_image->global->running_generation) { + level = APLOG_DEBUG; /* common to get these at restart time */ + } + else if (requests_this_child == INT_MAX + || ((requests_this_child == ap_max_requests_per_child) + && ap_max_requests_per_child)) { + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00272) + "apr_proc_mutex_%s failed " + "before this child process served any requests.", + func); + clean_child_exit(APEXIT_CHILDSICK); + } + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00273) + "apr_proc_mutex_%s failed. Attempting to " + "shutdown process gracefully.", func); + signal_threads(ST_GRACEFUL); +} + +static void * APR_THREAD_FUNC listener_thread(apr_thread_t *thd, void * dummy) +{ + proc_info * ti = dummy; + int process_slot = ti->pid; + void *csd = NULL; + apr_pool_t *ptrans = NULL; /* Pool for per-transaction stuff */ + apr_status_t rv; + ap_listen_rec *lr = NULL; + int have_idle_worker = 0; + int last_poll_idx = 0; + + free(ti); + + /* Unblock the signal used to wake this thread up, and set a handler for + * it. + */ + apr_signal(LISTENER_SIGNAL, dummy_signal_handler); + unblock_signal(LISTENER_SIGNAL); + + /* TODO: Switch to a system where threads reuse the results from earlier + poll calls - manoj */ + while (1) { + /* TODO: requests_this_child should be synchronized - aaron */ + if (requests_this_child <= 0) { + check_infinite_requests(); + } + if (listener_may_exit) break; + + if (!have_idle_worker) { + rv = ap_queue_info_wait_for_idler(worker_queue_info, NULL); + if (APR_STATUS_IS_EOF(rv)) { + break; /* we've been signaled to die now */ + } + else if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "apr_queue_info_wait failed. Attempting to " + " shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + break; + } + have_idle_worker = 1; + } + + /* We've already decremented the idle worker count inside + * ap_queue_info_wait_for_idler. */ + + if ((rv = SAFE_ACCEPT(apr_proc_mutex_lock(my_bucket->mutex))) + != APR_SUCCESS) { + + if (!listener_may_exit) { + accept_mutex_error("lock", rv, process_slot); + } + break; /* skip the lock release */ + } + + if (!my_bucket->listeners->next) { + /* Only one listener, so skip the poll */ + lr = my_bucket->listeners; + } + else { + while (!listener_may_exit) { + apr_int32_t numdesc; + const apr_pollfd_t *pdesc; + + rv = apr_pollset_poll(worker_pollset, -1, &numdesc, &pdesc); + if (rv != APR_SUCCESS) { + if (APR_STATUS_IS_EINTR(rv)) { + continue; + } + + /* 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, ap_server_conf, APLOGNO(03137) + "apr_pollset_poll: (listen)"); + signal_threads(ST_GRACEFUL); + } + + if (listener_may_exit) break; + + /* We can always use pdesc[0], but sockets at position N + * could end up completely starved of attention in a very + * busy server. Therefore, we round-robin across the + * returned set of descriptors. While it is possible that + * the returned set of descriptors might flip around and + * continue to starve some sockets, we happen to know the + * internal pollset implementation retains ordering + * stability of the sockets. Thus, the round-robin should + * ensure that a socket will eventually be serviced. + */ + if (last_poll_idx >= numdesc) + last_poll_idx = 0; + + /* Grab a listener record from the client_data of the poll + * descriptor, and advance our saved index to round-robin + * the next fetch. + * + * ### hmm... this descriptor might have POLLERR rather + * ### than POLLIN + */ + lr = pdesc[last_poll_idx++].client_data; + break; + + } /* while */ + + } /* if/else */ + + if (!listener_may_exit) { + /* the following pops a recycled ptrans pool off a stack */ + ap_queue_info_pop_pool(worker_queue_info, &ptrans); + if (ptrans == NULL) { + /* we can't use a recycled transaction pool this time. + * create a new transaction pool */ + apr_allocator_t *allocator; + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + apr_pool_create_ex(&ptrans, pconf, NULL, allocator); + apr_allocator_owner_set(allocator, ptrans); + apr_pool_tag(ptrans, "transaction"); + } + rv = lr->accept_func(&csd, lr, ptrans); + /* later we trash rv and rely on csd to indicate success/failure */ + AP_DEBUG_ASSERT(rv == APR_SUCCESS || !csd); + + if (rv == APR_EGENERAL) { + /* E[NM]FILE, ENOMEM, etc */ + resource_shortage = 1; + signal_threads(ST_GRACEFUL); + } + if ((rv = SAFE_ACCEPT(apr_proc_mutex_unlock(my_bucket->mutex))) + != APR_SUCCESS) { + + if (listener_may_exit) { + break; + } + accept_mutex_error("unlock", rv, process_slot); + } + if (csd != NULL) { + rv = ap_queue_push_socket(worker_queue, csd, NULL, ptrans); + if (rv) { + /* trash the connection; we couldn't queue the connected + * socket to a worker + */ + apr_socket_close(csd); + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(03138) + "ap_queue_push_socket failed"); + } + else { + have_idle_worker = 0; + } + } + } + else { + if ((rv = SAFE_ACCEPT(apr_proc_mutex_unlock(my_bucket->mutex))) + != APR_SUCCESS) { + int level = APLOG_EMERG; + + if (ap_scoreboard_image->parent[process_slot].generation != + ap_scoreboard_image->global->running_generation) { + level = APLOG_DEBUG; /* common to get these at restart time */ + } + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00274) + "apr_proc_mutex_unlock failed. Attempting to " + "shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + } + break; + } + } + + ap_close_listeners_ex(my_bucket->listeners); + ap_queue_info_free_idle_pools(worker_queue_info); + ap_queue_term(worker_queue); + dying = 1; + ap_scoreboard_image->parent[process_slot].quiescing = 1; + + /* wake up the main thread */ + kill(ap_my_pid, SIGTERM); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +/* XXX For ungraceful termination/restart, we definitely don't want to + * wait for active connections to finish but we may want to wait + * for idle workers to get out of the queue code and release mutexes, + * since those mutexes are cleaned up pretty soon and some systems + * may not react favorably (i.e., segfault) if operations are attempted + * on cleaned-up mutexes. + */ +static void * APR_THREAD_FUNC worker_thread(apr_thread_t *thd, void * dummy) +{ + proc_info * ti = dummy; + int process_slot = ti->pid; + int thread_slot = ti->tid; + apr_socket_t *csd = NULL; + apr_bucket_alloc_t *bucket_alloc; + apr_pool_t *last_ptrans = NULL; + apr_pool_t *ptrans; /* Pool for per-transaction stuff */ + apr_status_t rv; + int is_idle = 0; + + free(ti); + + 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 = retained->mpm->my_generation; + ap_update_child_status_from_indexes(process_slot, thread_slot, + SERVER_STARTING, NULL); + +#ifdef HAVE_PTHREAD_KILL + apr_signal(WORKER_SIGNAL, dummy_signal_handler); + unblock_signal(WORKER_SIGNAL); +#endif + + while (!workers_may_exit) { + if (!is_idle) { + rv = ap_queue_info_set_idle(worker_queue_info, last_ptrans); + last_ptrans = NULL; + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "ap_queue_info_set_idle failed. Attempting to " + "shutdown process gracefully."); + signal_threads(ST_GRACEFUL); + break; + } + is_idle = 1; + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + SERVER_READY, NULL); +worker_pop: + if (workers_may_exit) { + break; + } + rv = ap_queue_pop_socket(worker_queue, &csd, &ptrans); + + if (rv != APR_SUCCESS) { + /* We get APR_EOF during a graceful shutdown once all the connections + * accepted by this server process have been handled. + */ + if (APR_STATUS_IS_EOF(rv)) { + break; + } + /* We get APR_EINTR whenever ap_queue_pop_*() has been interrupted + * from an explicit call to ap_queue_interrupt_all(). This allows + * us to unblock threads stuck in ap_queue_pop_*() when a shutdown + * is pending. + * + * If workers_may_exit is set and this is ungraceful termination/ + * restart, we are bound to get an error on some systems (e.g., + * AIX, which sanity-checks mutex operations) since the queue + * may have already been cleaned up. Don't log the "error" if + * workers_may_exit is set. + */ + else if (APR_STATUS_IS_EINTR(rv)) { + goto worker_pop; + } + /* We got some other error. */ + else if (!workers_may_exit) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(03139) + "ap_queue_pop_socket failed"); + } + continue; + } + is_idle = 0; + worker_sockets[thread_slot] = csd; + bucket_alloc = apr_bucket_alloc_create(ptrans); + process_socket(thd, ptrans, csd, process_slot, thread_slot, bucket_alloc); + worker_sockets[thread_slot] = NULL; + requests_this_child--; + apr_pool_clear(ptrans); + last_ptrans = ptrans; + } + + ap_update_child_status_from_indexes(process_slot, thread_slot, + dying ? SERVER_DEAD + : SERVER_GRACEFUL, NULL); + + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static int check_signal(int signum) +{ + switch (signum) { + case SIGTERM: + case SIGINT: + return 1; + } + return 0; +} + +static void create_listener_thread(thread_starter *ts) +{ + int my_child_num = ts->child_num_arg; + apr_threadattr_t *thread_attr = ts->threadattr; + proc_info *my_info; + apr_status_t rv; + + my_info = (proc_info *)ap_malloc(sizeof(proc_info)); + my_info->pid = my_child_num; + my_info->tid = -1; /* listener thread doesn't have a thread slot */ + my_info->sd = 0; + rv = ap_thread_create(&ts->listener, thread_attr, listener_thread, + my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00275) + "ap_thread_create: unable to create listener thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + apr_os_thread_get(&listener_os_thread, ts->listener); +} + +static void setup_threads_runtime(void) +{ + ap_listen_rec *lr; + apr_status_t rv; + + /* All threads (listener, workers) and synchronization objects (queues, + * pollset, mutexes...) created here should have at least the lifetime of + * the connections they handle (i.e. ptrans). We can't use this thread's + * self pool because all these objects survive it, nor use pchild or pconf + * directly because this starter thread races with other modules' runtime, + * nor finally pchild (or subpool thereof) because it is killed explicitly + * before pconf (thus connections/ptrans can live longer, which matters in + * ONE_PROCESS mode). So this leaves us with a subpool of pconf, created + * before any ptrans hence destroyed after. + */ + apr_pool_create(&pruntime, pconf); + apr_pool_tag(pruntime, "mpm_runtime"); + + /* We must create the fd queues before we start up the listener + * and worker threads. */ + rv = ap_queue_create(&worker_queue, threads_per_child, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03140) + "ap_queue_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + rv = ap_queue_info_create(&worker_queue_info, pruntime, + threads_per_child, -1); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03141) + "ap_queue_info_create() failed"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create the main pollset */ + rv = apr_pollset_create(&worker_pollset, num_listensocks, pruntime, + APR_POLLSET_NOCOPY); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(03285) + "Couldn't create pollset in thread;" + " check system or user limits"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + for (lr = my_bucket->listeners; lr != NULL; lr = lr->next) { + apr_pollfd_t *pfd = apr_pcalloc(pruntime, sizeof *pfd); + + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = lr->sd; + pfd->reqevents = APR_POLLIN; + pfd->client_data = lr; + + rv = apr_pollset_add(worker_pollset, pfd); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(03286) + "Couldn't create add listener to pollset;" + " check system or user limits"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + lr->accept_func = ap_unixd_accept; + } + + worker_sockets = apr_pcalloc(pruntime, threads_per_child * + sizeof(apr_socket_t *)); +} + +/* XXX under some circumstances not understood, children can get stuck + * in start_threads forever trying to take over slots which will + * never be cleaned up; for now there is an APLOG_DEBUG message issued + * every so often when this condition occurs + */ +static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy) +{ + thread_starter *ts = dummy; + apr_thread_t **threads = ts->threads; + apr_threadattr_t *thread_attr = ts->threadattr; + int my_child_num = ts->child_num_arg; + proc_info *my_info; + apr_status_t rv; + int threads_created = 0; + int listener_started = 0; + int prev_threads_created; + int loops, i; + + loops = prev_threads_created = 0; + while (1) { + /* threads_per_child does not include the listener thread */ + for (i = 0; i < threads_per_child; i++) { + int status = ap_scoreboard_image->servers[my_child_num][i].status; + + if (status != SERVER_GRACEFUL && status != SERVER_DEAD) { + continue; + } + + my_info = (proc_info *)ap_malloc(sizeof(proc_info)); + my_info->pid = my_child_num; + my_info->tid = i; + my_info->sd = 0; + + /* We are creating threads right now */ + ap_update_child_status_from_indexes(my_child_num, i, + SERVER_STARTING, NULL); + /* We let each thread update its own scoreboard entry. This is + * done because it lets us deal with tid better. + */ + rv = ap_thread_create(&threads[i], thread_attr, + worker_thread, my_info, pruntime); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03142) + "ap_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + threads_created++; + } + /* Start the listener only when there are workers available */ + if (!listener_started && threads_created) { + create_listener_thread(ts); + listener_started = 1; + } + if (start_thread_may_exit || threads_created == threads_per_child) { + break; + } + /* wait for previous generation to clean up an entry */ + apr_sleep(apr_time_from_sec(1)); + ++loops; + if (loops % 120 == 0) { /* every couple of minutes */ + if (prev_threads_created == threads_created) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "child %" APR_PID_T_FMT " isn't taking over " + "slots very quickly (%d of %d)", + ap_my_pid, threads_created, threads_per_child); + } + prev_threads_created = threads_created; + } + } + + /* What state should this child_main process be listed as in the + * scoreboard...? + * ap_update_child_status_from_indexes(my_child_num, i, SERVER_STARTING, + * (request_rec *) NULL); + * + * This state should be listed separately in the scoreboard, in some kind + * of process_status, not mixed in with the worker threads' status. + * "life_status" is almost right, but it's in the worker's structure, and + * the name could be clearer. gla + */ + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +static void join_workers(apr_thread_t *listener, apr_thread_t **threads, + int mode) +{ + int i; + apr_status_t rv, thread_rv; + + if (listener) { + int iter; + + /* deal with a rare timing window which affects waking up the + * listener thread... if the signal sent to the listener thread + * is delivered between the time it verifies that the + * listener_may_exit flag is clear and the time it enters a + * blocking syscall, the signal didn't do any good... work around + * that by sleeping briefly and sending it again + */ + + iter = 0; + while (iter < 10 && +#ifdef HAVE_PTHREAD_KILL + pthread_kill(*listener_os_thread, 0) +#else + kill(ap_my_pid, 0) +#endif + == 0) { + /* listener not dead yet */ + apr_sleep(apr_time_make(0, 500000)); + wakeup_listener(); + ++iter; + } + if (iter >= 10) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00276) + "the listener thread didn't exit"); + } + else { + rv = apr_thread_join(&thread_rv, listener); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00277) + "apr_thread_join: unable to join listener thread"); + } + } + } + + for (i = 0; i < threads_per_child; i++) { + if (threads[i]) { /* if we ever created this thread */ + if (mode != ST_GRACEFUL) { +#ifdef HAVE_PTHREAD_KILL + apr_os_thread_t *worker_os_thread; + + apr_os_thread_get(&worker_os_thread, threads[i]); + pthread_kill(*worker_os_thread, WORKER_SIGNAL); +#endif + } + + rv = apr_thread_join(&thread_rv, threads[i]); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00278) + "apr_thread_join: unable to join worker " + "thread %d", + i); + } + } + } +} + +static void join_start_thread(apr_thread_t *start_thread_id) +{ + apr_status_t rv, thread_rv; + + start_thread_may_exit = 1; /* tell it to give up in case it is still + * trying to take over slots from a + * previous generation + */ + rv = apr_thread_join(&thread_rv, start_thread_id); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00279) + "apr_thread_join: unable to join the start " + "thread"); + } +} + +static void child_main(int child_num_arg, int child_bucket) +{ + apr_thread_t **threads; + apr_status_t rv; + thread_starter *ts; + apr_threadattr_t *thread_attr; + apr_thread_t *start_thread_id; + int i; + + /* for benefit of any hooks that run as this child initializes */ + retained->mpm->mpm_state = AP_MPMQ_STARTING; + + ap_my_pid = getpid(); + ap_fatal_signal_child_setup(ap_server_conf); + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + +#if AP_HAS_THREAD_LOCAL + if (!one_process) { + apr_thread_t *thd = NULL; + if ((rv = ap_thread_main_create(&thd, pchild))) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10375) + "Couldn't initialize child main thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +#endif + + /* close unused listeners and pods */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (i != child_bucket) { + ap_close_listeners_ex(all_buckets[i].listeners); + ap_mpm_podx_close(all_buckets[i].pod); + } + } + + /*stuff to do before we switch id's, so we have permissions.*/ + ap_reopen_scoreboard(pchild, NULL, 0); + + rv = SAFE_ACCEPT(apr_proc_mutex_child_init(&my_bucket->mutex, + apr_proc_mutex_lockfile(my_bucket->mutex), + pchild)); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00280) + "Couldn't initialize cross-process lock in child"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* done with init critical section */ + if (ap_run_drop_privileges(pchild, ap_server_conf)) { + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Just use the standard apr_setup_signal_thread to block all signals + * from being received. The child processes no longer use signals for + * any communication with the parent process. Let's also do this before + * child_init() hooks are called and possibly create threads that + * otherwise could "steal" (implicitly) MPM's signals. + */ + rv = apr_setup_signal_thread(); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00281) + "Couldn't initialize signal thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_run_child_init(pchild, ap_server_conf); + + if (ap_max_requests_per_child) { + requests_this_child = ap_max_requests_per_child; + } + else { + /* coding a value of zero means infinity */ + requests_this_child = INT_MAX; + } + + /* Setup threads */ + + /* Globals used by signal_threads() so to be initialized before */ + setup_threads_runtime(); + + /* clear the storage; we may not create all our threads immediately, + * and we want a 0 entry to indicate a thread which was not created + */ + threads = (apr_thread_t **)ap_calloc(1, + sizeof(apr_thread_t *) * threads_per_child); + ts = (thread_starter *)apr_palloc(pchild, sizeof(*ts)); + + apr_threadattr_create(&thread_attr, pchild); + /* 0 means PTHREAD_CREATE_JOINABLE */ + apr_threadattr_detach_set(thread_attr, 0); + + if (ap_thread_stacksize != 0) { + rv = apr_threadattr_stacksize_set(thread_attr, ap_thread_stacksize); + if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(02435) + "WARNING: ThreadStackSize of %" APR_SIZE_T_FMT " is " + "inappropriate, using default", + ap_thread_stacksize); + } + } + + ts->threads = threads; + ts->listener = NULL; + ts->child_num_arg = child_num_arg; + ts->threadattr = thread_attr; + + rv = ap_thread_create(&start_thread_id, thread_attr, start_threads, + ts, pchild); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00282) + "ap_thread_create: unable to create worker thread"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + /* If we are only running in one_process mode, we will want to + * still handle signals. */ + if (one_process) { + /* Block until we get a terminating signal. */ + apr_signal_thread(check_signal); + /* make sure the start thread has finished; signal_threads() + * and join_workers() depend on that + */ + /* XXX join_start_thread() won't be awakened if one of our + * threads encounters a critical error and attempts to + * shutdown this child + */ + join_start_thread(start_thread_id); + signal_threads(ST_UNGRACEFUL); /* helps us terminate a little more + * quickly than the dispatch of the signal thread + * beats the Pipe of Death and the browsers + */ + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + join_workers(ts->listener, threads, ST_UNGRACEFUL); + } + 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 MaxConnectionsPerChild) it will send us SIGTERM + */ + apr_signal(SIGTERM, dummy_signal_handler); + unblock_signal(SIGTERM); + /* Watch for any messages from the parent over the POD */ + while (1) { + rv = ap_mpm_podx_check(my_bucket->pod); + if (rv == AP_MPM_PODX_NORESTART) { + /* see if termination was triggered while we slept */ + switch(terminate_mode) { + case ST_GRACEFUL: + rv = AP_MPM_PODX_GRACEFUL; + break; + case ST_UNGRACEFUL: + rv = AP_MPM_PODX_RESTART; + break; + } + } + if (rv == AP_MPM_PODX_GRACEFUL || rv == AP_MPM_PODX_RESTART) { + /* make sure the start thread has finished; + * signal_threads() and join_workers depend on that + */ + join_start_thread(start_thread_id); + signal_threads(rv == AP_MPM_PODX_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL); + break; + } + } + + /* A terminating signal was received. Now join each of the + * workers to clean them up. + * If the worker already exited, then the join frees + * their resources and returns. + * If the worker hasn't exited, then this blocks until + * they have (then cleans up). + */ + join_workers(ts->listener, threads, + rv == AP_MPM_PODX_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL); + } + + free(threads); + + clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0); +} + +static int make_child(server_rec *s, int slot, int bucket) +{ + int pid; + + if (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; + } + + if (one_process) { + my_bucket = &all_buckets[0]; + + worker_note_child_started(slot, getpid()); + child_main(slot, 0); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + if ((pid = fork()) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00283) + "fork: Unable to fork new process"); + /* fork didn't succeed. There's no need to touch the scoreboard; + * if we were trying to replace a failed child process, then + * server_main_loop() marked its workers SERVER_DEAD, and if + * we were trying to replace a child process that exited normally, + * its worker_thread()s left SERVER_DEAD or SERVER_GRACEFUL behind. + */ + + /* In case system resources are maxxed out, we don't want + Apache running away with the CPU trying to fork over and + over and over again. */ + apr_sleep(apr_time_from_sec(10)); + + return -1; + } + + if (!pid) { +#if AP_HAS_THREAD_LOCAL + ap_thread_current_after_fork(); +#endif + + my_bucket = &all_buckets[bucket]; + +#ifdef HAVE_BINDPROCESSOR + /* By default, AIX binds to a single processor. This bit unbinds + * children which will then bind to another CPU. + */ + int status = bindprocessor(BINDPROCESS, (int)getpid(), + PROCESSOR_CLASS_ANY); + if (status != OK) + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00284) + "processor unbind failed"); +#endif + RAISE_SIGSTOP(MAKE_CHILD); + + apr_signal(SIGTERM, just_die); + child_main(slot, bucket); + /* NOTREACHED */ + ap_assert(0); + return -1; + } + + 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. + */ + worker_note_child_lost_slot(slot, pid); + } + ap_scoreboard_image->parent[slot].quiescing = 0; + worker_note_child_started(slot, pid); + return 0; +} + +/* start up a bunch of children */ +static void startup_children(int number_to_start) +{ + int i; + + for (i = 0; number_to_start && i < ap_daemons_limit; ++i) { + if (ap_scoreboard_image->parent[i].pid != 0) { + continue; + } + if (make_child(ap_server_conf, i, i % retained->mpm->num_buckets) < 0) { + break; + } + --number_to_start; + } +} + +static void perform_idle_server_maintenance(int child_bucket) +{ + int num_buckets = retained->mpm->num_buckets; + int idle_thread_count; + process_score *ps; + int free_length; + int totally_free_length = 0; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead; + int total_non_dead; + int active_thread_count = 0; + int i, j; + + /* initialize the free_list */ + free_length = 0; + + idle_thread_count = 0; + last_non_dead = -1; + total_non_dead = 0; + + for (i = 0; i < ap_daemons_limit; ++i) { + /* Initialization to satisfy the compiler. It doesn't know + * that threads_per_child is always > 0 */ + int any_dying_threads = 0; + int any_dead_threads = 0; + int all_dead_threads = 1; + int child_threads_active = 0; + + if (num_buckets > 1 && (i % num_buckets) != child_bucket) { + /* We only care about child_bucket in this call */ + continue; + } + if (i >= retained->max_daemons_limit && + totally_free_length == retained->idle_spawn_rate[child_bucket]) { + /* short cut if all active processes have been examined and + * enough empty scoreboard slots have been found + */ + break; + } + ps = &ap_scoreboard_image->parent[i]; + for (j = 0; j < threads_per_child; j++) { + int status = ap_scoreboard_image->servers[i][j].status; + + /* XXX any_dying_threads is probably no longer needed GLA */ + any_dying_threads = any_dying_threads || + (status == SERVER_GRACEFUL); + any_dead_threads = any_dead_threads || (status == SERVER_DEAD); + all_dead_threads = all_dead_threads && + (status == SERVER_DEAD || + status == SERVER_GRACEFUL); + + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (ps->pid != 0) { /* XXX just set all_dead_threads in outer for + loop if no pid? not much else matters */ + if (status <= SERVER_READY && + !ps->quiescing && + ps->generation == retained->mpm->my_generation) { + ++idle_thread_count; + } + if (status >= SERVER_READY && status < SERVER_GRACEFUL) { + ++child_threads_active; + } + } + } + active_thread_count += child_threads_active; + if (any_dead_threads + && totally_free_length < retained->idle_spawn_rate[child_bucket] + && free_length < MAX_SPAWN_RATE / num_buckets + && (!ps->pid /* no process in the slot */ + || ps->quiescing)) { /* or at least one is going away */ + if (all_dead_threads) { + /* great! we prefer these, because the new process can + * start more threads sooner. So prioritize this slot + * by putting it ahead of any slots with active threads. + * + * first, make room by moving a slot that's potentially still + * in use to the end of the array + */ + free_slots[free_length] = free_slots[totally_free_length]; + free_slots[totally_free_length++] = i; + } + else { + /* slot is still in use - back of the bus + */ + free_slots[free_length] = i; + } + ++free_length; + } + else if (child_threads_active == threads_per_child) { + had_healthy_child = 1; + } + /* XXX if (!ps->quiescing) is probably more reliable GLA */ + if (!any_dying_threads) { + ++total_non_dead; + } + if (ps->pid != 0) { + last_non_dead = i; + } + } + + retained->max_daemons_limit = last_non_dead + 1; + + if (retained->sick_child_detected) { + if (had_healthy_child) { + /* Assume this is a transient error, even though it may not be. Leave + * the server up in case it is able to serve some requests or the + * problem will be resolved. + */ + retained->sick_child_detected = 0; + } + else if (child_bucket < num_buckets - 1) { + /* check for had_healthy_child up to the last child bucket */ + return; + } + else { + /* looks like a basket case, as no child ever fully initialized; give up. + */ + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, + ap_server_conf, APLOGNO(02325) + "A resource shortage or other unrecoverable failure " + "was encountered before any child process initialized " + "successfully... httpd is exiting!"); + /* the child already logged the failure details */ + return; + } + } + + if (idle_thread_count > max_spare_threads / num_buckets) { + /* Kill off one child */ + ap_mpm_podx_signal(all_buckets[child_bucket].pod, + AP_MPM_PODX_GRACEFUL); + retained->idle_spawn_rate[child_bucket] = 1; + } + else if (idle_thread_count < min_spare_threads / num_buckets) { + /* terminate the free list */ + if (free_length == 0) { /* scoreboard is full, can't fork */ + + if (active_thread_count >= max_workers / num_buckets) { + /* no threads are "inactive" - starting, stopping, etc. */ + /* have we reached MaxRequestWorkers, or just getting close? */ + if (0 == idle_thread_count) { + if (!retained->maxclients_reported) { + /* only report this condition once */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00286) + "server reached MaxRequestWorkers " + "setting, consider raising the " + "MaxRequestWorkers setting"); + retained->maxclients_reported = 1; + } + } else { + if (!retained->near_maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00287) + "server is within MinSpareThreads of " + "MaxRequestWorkers, consider raising the " + "MaxRequestWorkers setting"); + retained->near_maxclients_reported = 1; + } + } + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, + ap_server_conf, APLOGNO(00288) + "scoreboard is full, not at MaxRequestWorkers"); + } + retained->idle_spawn_rate[child_bucket] = 1; + } + else { + if (free_length > retained->idle_spawn_rate[child_bucket]) { + free_length = retained->idle_spawn_rate[child_bucket]; + } + if (retained->idle_spawn_rate[child_bucket] >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, + ap_server_conf, APLOGNO(00289) + "server seems busy, (you may need " + "to increase StartServers, ThreadsPerChild " + "or Min/MaxSpareThreads), " + "spawning %d children, there are around %d idle " + "threads, and %d total children", free_length, + idle_thread_count, total_non_dead); + } + for (i = 0; i < free_length; ++i) { + make_child(ap_server_conf, free_slots[i], child_bucket); + } + /* 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 (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; + } + else if (retained->idle_spawn_rate[child_bucket] + < MAX_SPAWN_RATE / num_buckets) { + retained->idle_spawn_rate[child_bucket] *= 2; + } + } + } + else { + retained->idle_spawn_rate[child_bucket] = 1; + } +} + +static void server_main_loop(int remaining_children_to_start) +{ + int num_buckets = retained->mpm->num_buckets; + int successive_kills = 0; + ap_generation_t old_gen; + int child_slot; + apr_exit_why_e exitwhy; + int status, processed_status; + apr_proc_t pid; + int i; + + while (!retained->mpm->restart_pending && !retained->mpm->shutdown_pending) { + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); + + if (pid.pid != -1) { + processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); + if (processed_status == APEXIT_CHILDFATAL) { + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->mpm->my_generation) { + retained->mpm->shutdown_pending = 1; + child_fatal = 1; + return; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00290) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + retained->sick_child_detected = 1; + } + } + else if (processed_status == APEXIT_CHILDSICK) { + /* tell perform_idle_server_maintenance to check into this + * on the next timer pop + */ + retained->sick_child_detected = 1; + } + /* non-fatal death... note that it's gone in the scoreboard. */ + if (child_slot >= 0) { + process_score *ps; + + for (i = 0; i < threads_per_child; i++) + ap_update_child_status_from_indexes(child_slot, i, + SERVER_DEAD, NULL); + + worker_note_child_killed(child_slot, 0, 0); + ps = &ap_scoreboard_image->parent[child_slot]; + ps->quiescing = 0; + if (processed_status == APEXIT_CHILDSICK) { + /* resource shortage, minimize the fork rate */ + retained->idle_spawn_rate[child_slot % num_buckets] = 1; + } + else if (remaining_children_to_start + && child_slot < ap_daemons_limit) { + /* we're still doing a 1-for-1 replacement of dead + * children with new children + */ + make_child(ap_server_conf, child_slot, + child_slot % num_buckets); + --remaining_children_to_start; + } + } + 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 (processed_status == APEXIT_CHILDSICK + && old_gen == retained->mpm->my_generation) { + /* resource shortage, minimize the fork rate */ + for (i = 0; i < num_buckets; i++) { + retained->idle_spawn_rate[i] = 1; + } + } +#if APR_HAS_OTHER_CHILD + } + else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, + status) == 0) { + /* handled */ +#endif + } + else if (retained->mpm->was_graceful) { + /* Great, we've probably just lost a slot in the + * scoreboard. Somehow we don't know about this child. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(00291) + "long lost child came home! (pid %ld)", + (long)pid.pid); + } + /* Don't perform idle maintenance when a child dies, + * only do it when there's a timeout. Remember only a + * finite number of children can die, and it's pretty + * pathological for a lot to die suddenly. If a child is + * killed by a signal (faulting) we want to restart it ASAP + * though, up to 3 successive faults or we stop this until + * a timeout happens again (to avoid the flood of fork()ed + * processes that keep being killed early). + */ + if (child_slot < 0 || !APR_PROC_CHECK_SIGNALED(exitwhy)) { + continue; + } + if (++successive_kills >= 3) { + if (successive_kills % 10 == 3) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(10393) + "children are killed successively!"); + } + continue; + } + ++remaining_children_to_start; + } + else { + successive_kills = 0; + } + + if (remaining_children_to_start) { + /* we hit a 1 second timeout in which none of the previous + * generation of children needed to be reaped... so assume + * they're all done, and pick up the slack if any is left. + */ + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + /* In any event we really shouldn't do the code below because + * few of the servers we just started are in the IDLE state + * yet, so we'd mistakenly create an extra server. + */ + continue; + } + + for (i = 0; i < num_buckets; i++) { + perform_idle_server_maintenance(i); + } + } +} + +static int worker_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +{ + int num_buckets = retained->mpm->num_buckets; + int remaining_children_to_start; + int i; + + ap_log_pid(pconf, ap_pid_fname); + + if (!retained->mpm->was_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + return !OK; + } + /* fix the generation number in the global score; we just got a new, + * cleared scoreboard + */ + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + } + + ap_unixd_mpm_set_signals(pconf, one_process); + + /* Don't thrash since num_buckets depends on the + * system and the number of online CPU cores... + */ + if (ap_daemons_limit < num_buckets) + ap_daemons_limit = num_buckets; + if (ap_daemons_to_start < num_buckets) + ap_daemons_to_start = num_buckets; + /* We want to create as much children at a time as the number of buckets, + * so to optimally accept connections (evenly distributed across buckets). + * Thus min_spare_threads should at least maintain num_buckets children, + * and max_spare_threads allow num_buckets more children w/o triggering + * immediately (e.g. num_buckets idle threads margin, one per bucket). + */ + if (min_spare_threads < threads_per_child * (num_buckets - 1) + num_buckets) + min_spare_threads = threads_per_child * (num_buckets - 1) + num_buckets; + if (max_spare_threads < min_spare_threads + (threads_per_child + 1) * num_buckets) + max_spare_threads = min_spare_threads + (threads_per_child + 1) * num_buckets; + + /* If we're doing a graceful_restart then we're going to see a lot + * of children exiting immediately when we get into the main loop + * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty + * rapidly... and for each one that exits we may start a new one, until + * there are at least min_spare_threads idle threads, counting across + * all children. But we may be permitted to start more children than + * that, so we'll just keep track of how many we're + * supposed to start up without the 1 second penalty between each fork. + */ + remaining_children_to_start = ap_daemons_to_start; + if (remaining_children_to_start > ap_daemons_limit) { + remaining_children_to_start = ap_daemons_limit; + } + if (!retained->mpm->was_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 */ + retained->hold_off_on_exponential_spawning = 10; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00292) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00293) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00294) + "Accept mutex: %s (default: %s)", + (all_buckets[0].mutex) + ? apr_proc_mutex_name(all_buckets[0].mutex) + : "none", + apr_proc_mutex_defname()); + retained->mpm->mpm_state = AP_MPMQ_RUNNING; + + server_main_loop(remaining_children_to_start); + retained->mpm->mpm_state = AP_MPMQ_STOPPING; + + if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { + /* Time to shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + worker_note_child_killed); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, + ap_server_conf, APLOGNO(00295) "caught SIGTERM, shutting down"); + } + return DONE; + } + + if (retained->mpm->shutdown_pending) { + /* Time to gracefully shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + int active_children; + int index; + apr_time_t cutoff = 0; + + /* Close our listeners, and then ask our children to do same */ + ap_close_listeners(); + + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + ap_relieve_child_processes(worker_note_child_killed); + + if (!child_fatal) { + /* cleanup pid file on normal shutdown */ + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00296) + "caught " AP_SIG_GRACEFUL_STOP_STRING + ", shutting down gracefully"); + } + + if (ap_graceful_shutdown_timeout) { + cutoff = apr_time_now() + + apr_time_from_sec(ap_graceful_shutdown_timeout); + } + + /* Don't really exit until each child has finished */ + retained->mpm->shutdown_pending = 0; + do { + /* Pause for a second */ + apr_sleep(apr_time_from_sec(1)); + + /* Relieve any children which have now exited */ + ap_relieve_child_processes(worker_note_child_killed); + + active_children = 0; + for (index = 0; index < ap_daemons_limit; ++index) { + if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) { + active_children = 1; + /* Having just one child is enough to stay around */ + break; + } + } + } while (!retained->mpm->shutdown_pending && active_children && + (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff)); + + /* We might be here because we received SIGTERM, either + * way, try and make sure that all of our processes are + * really dead. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_RESTART); + } + ap_reclaim_child_processes(1, worker_note_child_killed); + + return DONE; + } + + /* we've been told to restart */ + if (one_process) { + /* not worth thinking about */ + return DONE; + } + + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++retained->mpm->my_generation; + ap_scoreboard_image->global->running_generation = retained->mpm->my_generation; + + if (!retained->mpm->is_ungraceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00297) + AP_SIG_GRACEFUL_STRING " received. Doing graceful restart"); + /* wake up the children...time to die. But we'll have more soon */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_GRACEFUL); + } + + /* This is mostly for debugging... so that we know what is still + * gracefully dealing with existing request. + */ + + } + else { + /* Kill 'em all. Since the child acts the same on the parents SIGTERM + * and a SIGHUP, we may as well use the same signal, because some user + * pthreads are stealing signals from us left and right. + */ + for (i = 0; i < num_buckets; i++) { + ap_mpm_podx_killpg(all_buckets[i].pod, ap_daemons_limit, + AP_MPM_PODX_RESTART); + } + + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + worker_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00298) + "SIGHUP received. Attempting to restart"); + } + + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int worker_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + int level_flags = 0; + int num_buckets = 0; + ap_listen_rec **listen_buckets; + apr_status_t rv; + char id[16]; + int i; + + pconf = p; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } + + if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "no listening sockets available, shutting down"); + return !OK; + } + + if (one_process) { + num_buckets = 1; + } + else if (retained->mpm->was_graceful) { + /* Preserve the number of buckets on graceful restarts. */ + num_buckets = retained->mpm->num_buckets; + } + if ((rv = ap_duplicate_listeners(pconf, ap_server_conf, + &listen_buckets, &num_buckets))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not duplicate listeners"); + return !OK; + } + + all_buckets = apr_pcalloc(pconf, num_buckets * sizeof(*all_buckets)); + for (i = 0; i < num_buckets; i++) { + if (!one_process && /* no POD in one_process mode */ + (rv = ap_mpm_podx_open(pconf, &all_buckets[i].pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); + return !OK; + } + /* Initialize cross-process accept lock (safe accept needed only) */ + if ((rv = SAFE_ACCEPT((apr_snprintf(id, sizeof id, "%i", i), + ap_proc_mutex_create(&all_buckets[i].mutex, + NULL, AP_ACCEPT_MUTEX_TYPE, + id, s, pconf, 0))))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not create accept mutex"); + return !OK; + } + all_buckets[i].listeners = listen_buckets[i]; + } + + if (retained->mpm->max_buckets < num_buckets) { + int new_max, *new_ptr; + new_max = retained->mpm->max_buckets * 2; + if (new_max < num_buckets) { + new_max = num_buckets; + } + new_ptr = (int *)apr_palloc(ap_pglobal, new_max * sizeof(int)); + if (retained->idle_spawn_rate) /* NULL at startup */ + memcpy(new_ptr, retained->idle_spawn_rate, + retained->mpm->num_buckets * sizeof(int)); + retained->idle_spawn_rate = new_ptr; + retained->mpm->max_buckets = new_max; + } + if (retained->mpm->num_buckets < num_buckets) { + int rate_max = 1; + /* If new buckets are added, set their idle spawn rate to + * the highest so far, so that they get filled as quickly + * as the existing ones. + */ + for (i = 0; i < retained->mpm->num_buckets; i++) { + if (rate_max < retained->idle_spawn_rate[i]) { + rate_max = retained->idle_spawn_rate[i]; + } + } + for (/* up to date i */; i < num_buckets; i++) { + retained->idle_spawn_rate[i] = rate_max; + } + } + retained->mpm->num_buckets = num_buckets; + + return OK; +} + +static int worker_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + int no_detach, debug, foreground; + apr_status_t rv; + const char *userdata_key = "mpm_worker_module"; + + debug = ap_exists_config_define("DEBUG"); + + if (debug) { + foreground = one_process = 1; + no_detach = 0; + } + else { + one_process = ap_exists_config_define("ONE_PROCESS"); + no_detach = ap_exists_config_define("NO_DETACH"); + foreground = ap_exists_config_define("FOREGROUND"); + } + + ap_mutex_register(pconf, AP_ACCEPT_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0); + + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->mpm = ap_unixd_mpm_get_retained_data(); + } + retained->mpm->mpm_state = AP_MPMQ_STARTING; + if (retained->mpm->baton != retained) { + retained->mpm->was_graceful = 0; + retained->mpm->baton = retained; + } + ++retained->mpm->module_loads; + + /* sigh, want this only the second time around */ + if (retained->mpm->module_loads == 2) { + if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); + rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND + : APR_PROC_DETACH_DAEMONIZE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00299) + "apr_proc_detach failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + 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; + max_spare_threads = DEFAULT_MAX_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD; + server_limit = DEFAULT_SERVER_LIMIT; + thread_limit = DEFAULT_THREAD_LIMIT; + ap_daemons_limit = server_limit; + threads_per_child = DEFAULT_THREADS_PER_CHILD; + max_workers = ap_daemons_limit * threads_per_child; + had_healthy_child = 0; + ap_extended_status = 0; + + return OK; +} + +static int worker_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->mpm->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00300) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of %d servers, decreasing to %d.", + server_limit, MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00301) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00302) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00303) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00304) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00305) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of %d threads, decreasing to %d.", + thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00306) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00307) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00308) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* you cannot change ThreadLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_thread_limit) { + retained->first_thread_limit = thread_limit; + } + else if (thread_limit != retained->first_thread_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00309) + "changing ThreadLimit to %d from original value of %d " + "not allowed during restart", + thread_limit, retained->first_thread_limit); + thread_limit = retained->first_thread_limit; + } + + if (threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00310) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of %d threads, decreasing to %d. " + "To increase, please see the ThreadLimit directive.", + threads_per_child, thread_limit, thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00311) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + threads_per_child, thread_limit); + } + threads_per_child = thread_limit; + } + else if (threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00312) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00313) + "ThreadsPerChild of %d not allowed, increasing to 1", + threads_per_child); + } + threads_per_child = 1; + } + + if (max_workers < threads_per_child) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00314) + "WARNING: MaxRequestWorkers of %d is less than " + "ThreadsPerChild of %d, increasing to %d. " + "MaxRequestWorkers must be at least as large " + "as the number of threads in a single server.", + max_workers, threads_per_child, threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00315) + "MaxRequestWorkers of %d is less than ThreadsPerChild " + "of %d, increasing to match", + max_workers, threads_per_child); + } + max_workers = threads_per_child; + } + + ap_daemons_limit = max_workers / threads_per_child; + + if (max_workers % threads_per_child) { + int tmp_max_workers = ap_daemons_limit * threads_per_child; + + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00316) + "WARNING: MaxRequestWorkers of %d is not an integer " + "multiple of ThreadsPerChild of %d, decreasing to nearest " + "multiple %d, for a maximum of %d servers.", + max_workers, threads_per_child, tmp_max_workers, + ap_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00317) + "MaxRequestWorkers of %d is not an integer multiple of " + "ThreadsPerChild of %d, decreasing to nearest " + "multiple %d", max_workers, threads_per_child, + tmp_max_workers); + } + max_workers = tmp_max_workers; + } + + if (ap_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00318) + "WARNING: MaxRequestWorkers of %d would require %d " + "servers and would exceed ServerLimit of %d, decreasing to %d. " + "To increase, please see the ServerLimit directive.", + max_workers, ap_daemons_limit, server_limit, + server_limit * threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00319) + "MaxRequestWorkers of %d would require %d servers and " + "exceed ServerLimit of %d, decreasing to %d", + max_workers, ap_daemons_limit, server_limit, + server_limit * threads_per_child); + } + ap_daemons_limit = server_limit; + } + + /* ap_daemons_to_start > ap_daemons_limit checked in worker_run() */ + if (ap_daemons_to_start < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00320) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00321) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00322) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", min_spare_threads); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00323) + "MinSpareThreads of %d not allowed, increasing to 1", + min_spare_threads); + } + min_spare_threads = 1; + } + + /* max_spare_threads < min_spare_threads + threads_per_child + * checked in worker_run() + */ + + return OK; +} + +static void worker_hooks(apr_pool_t *p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + one_process = 0; + + ap_hook_open_logs(worker_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + /* we need to set the MPM state before other pre-config hooks use MPM query + * to retrieve it, so register as REALLY_FIRST + */ + ap_hook_pre_config(worker_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + 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_get_name(worker_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + +static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + min_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + max_spare_threads = atoi(arg); + return NULL; +} + +static const char *set_max_workers (cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00324) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); + } + max_workers = atoi(arg); + return NULL; +} + +static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + threads_per_child = atoi(arg); + return NULL; +} + +static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + server_limit = atoi(arg); + return NULL; +} + +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + thread_limit = atoi(arg); + return NULL; +} + +static const command_rec worker_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup"), +AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, + "Minimum number of idle threads, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, + "Maximum number of idle threads"), +AP_INIT_TAKE1("MaxRequestWorkers", set_max_workers, NULL, RSRC_CONF, + "Maximum number of threads alive at the same time"), +AP_INIT_TAKE1("MaxClients", set_max_workers, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), +AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates"), +AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF, + "Maximum number of child processes for this run of Apache"), +AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum number of worker threads per child process for this run of Apache - Upper limit for ThreadsPerChild"), +AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_worker) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + worker_cmds, /* command apr_table_t */ + worker_hooks /* register_hooks */ +}; + diff --git a/server/mpm_common.c b/server/mpm_common.c new file mode 100644 index 0000000..9bcd760 --- /dev/null +++ b/server/mpm_common.c @@ -0,0 +1,578 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* The purpose of this file is to store the code that MOST mpm's will need + * this does not mean a function only goes into this file if every MPM needs + * it. It means that if a function is needed by more than one MPM, and + * future maintenance would be served by making the code common, then the + * function belongs here. + * + * This is going in src/main because it is not platform specific, it is + * specific to multi-process servers, but NOT to Unix. Which is why it + * does not belong in src/os/unix + */ + +#include "apr.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_strings.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_getopt.h" +#include "apr_optional.h" +#include "apr_allocator.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_main.h" +#include "mpm_common.h" +#include "mod_core.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "util_mutex.h" + +#include "scoreboard.h" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +#define DEFAULT_HOOK_LINKS \ + APR_HOOK_LINK(monitor) \ + APR_HOOK_LINK(drop_privileges) \ + APR_HOOK_LINK(mpm) \ + APR_HOOK_LINK(mpm_query) \ + APR_HOOK_LINK(mpm_register_timed_callback) \ + APR_HOOK_LINK(mpm_get_name) \ + APR_HOOK_LINK(end_generation) \ + APR_HOOK_LINK(child_status) \ + APR_HOOK_LINK(suspend_connection) \ + APR_HOOK_LINK(resume_connection) \ + APR_HOOK_LINK(child_stopping) + +#if AP_ENABLE_EXCEPTION_HOOK +APR_HOOK_STRUCT( + APR_HOOK_LINK(fatal_exception) + DEFAULT_HOOK_LINKS +) +AP_IMPLEMENT_HOOK_RUN_ALL(int, fatal_exception, + (ap_exception_info_t *ei), (ei), OK, DECLINED) +#else +APR_HOOK_STRUCT( + DEFAULT_HOOK_LINKS +) +#endif +AP_IMPLEMENT_HOOK_RUN_ALL(int, monitor, + (apr_pool_t *p, server_rec *s), (p, s), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, drop_privileges, + (apr_pool_t * pchild, server_rec * s), + (pchild, s), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm, + (apr_pool_t *pconf, apr_pool_t *plog, server_rec *s), + (pconf, plog, s), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm_query, + (int query_code, int *result, apr_status_t *_rv), + (query_code, result, _rv), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, mpm_register_timed_callback, + (apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton), + (t, cbfn, baton), APR_ENOTIMPL) +AP_IMPLEMENT_HOOK_VOID(end_generation, + (server_rec *s, ap_generation_t gen), + (s, gen)) +AP_IMPLEMENT_HOOK_VOID(child_status, + (server_rec *s, pid_t pid, ap_generation_t gen, int slot, mpm_child_status status), + (s,pid,gen,slot,status)) +AP_IMPLEMENT_HOOK_VOID(suspend_connection, + (conn_rec *c, request_rec *r), + (c, r)) +AP_IMPLEMENT_HOOK_VOID(resume_connection, + (conn_rec *c, request_rec *r), + (c, r)) +AP_IMPLEMENT_HOOK_VOID(child_stopping, + (apr_pool_t *pchild, int graceful), + (pchild, graceful)) + +/* hooks with no args are implemented last, after disabling APR hook probes */ +#if defined(APR_HOOK_PROBES_ENABLED) +#undef APR_HOOK_PROBES_ENABLED +#undef APR_HOOK_PROBE_ENTRY +#define APR_HOOK_PROBE_ENTRY(ud,ns,name,args) +#undef APR_HOOK_PROBE_RETURN +#define APR_HOOK_PROBE_RETURN(ud,ns,name,rv,args) +#undef APR_HOOK_PROBE_INVOKE +#define APR_HOOK_PROBE_INVOKE(ud,ns,name,src,args) +#undef APR_HOOK_PROBE_COMPLETE +#define APR_HOOK_PROBE_COMPLETE(ud,ns,name,src,rv,args) +#undef APR_HOOK_INT_DCL_UD +#define APR_HOOK_INT_DCL_UD +#endif +AP_IMPLEMENT_HOOK_RUN_FIRST(const char *, mpm_get_name, + (void), + (), NULL) + +typedef struct mpm_gen_info_t { + APR_RING_ENTRY(mpm_gen_info_t) link; + int gen; /* which gen? */ + int active; /* number of active processes */ + int done; /* gen finished? (whether or not active processes) */ +} mpm_gen_info_t; + +APR_RING_HEAD(mpm_gen_info_head_t, mpm_gen_info_t); +static struct mpm_gen_info_head_t *geninfo, *unused_geninfo; +static int gen_head_init; /* yuck */ + +/* variables representing config directives implemented here */ +AP_DECLARE_DATA const char *ap_pid_fname; +AP_DECLARE_DATA int ap_max_requests_per_child; +AP_DECLARE_DATA char ap_coredump_dir[MAX_STRING_LEN]; +AP_DECLARE_DATA int ap_coredumpdir_configured; +AP_DECLARE_DATA int ap_graceful_shutdown_timeout; +AP_DECLARE_DATA apr_uint32_t ap_max_mem_free; +AP_DECLARE_DATA apr_size_t ap_thread_stacksize; + +#define ALLOCATOR_MAX_FREE_DEFAULT (2048*1024) + +/* Set defaults for config directives implemented here. This is + * called from core's pre-config hook, so MPMs which need to override + * one of these should run their pre-config hook after that of core. + */ +void mpm_common_pre_config(apr_pool_t *pconf) +{ + ap_pid_fname = DEFAULT_PIDLOG; + ap_max_requests_per_child = 0; /* unlimited */ + apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir)); + ap_coredumpdir_configured = 0; + ap_graceful_shutdown_timeout = 0; /* unlimited */ + ap_max_mem_free = ALLOCATOR_MAX_FREE_DEFAULT; + ap_thread_stacksize = 0; /* use system default */ +} + +/* number of calls to wait_or_timeout between writable probes */ +#ifndef INTERVAL_OF_WRITABLE_PROBES +#define INTERVAL_OF_WRITABLE_PROBES 10 +#endif +static int wait_or_timeout_counter; + +AP_DECLARE(void) ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode, + apr_proc_t *ret, apr_pool_t *p, + server_rec *s) +{ + apr_status_t rv; + + ++wait_or_timeout_counter; + if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) { + wait_or_timeout_counter = 0; + ap_run_monitor(p, s); + } + + rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p); + ap_update_global_status(); + + if (APR_STATUS_IS_EINTR(rv)) { + ret->pid = -1; + return; + } + + if (APR_STATUS_IS_CHILD_DONE(rv)) { + return; + } + + apr_sleep(apr_time_from_sec(1)); + ret->pid = -1; +} + +#if defined(TCP_NODELAY) +void ap_sock_disable_nagle(apr_socket_t *s) +{ + /* The Nagle algorithm says that we should delay sending partial + * packets in hopes of getting more data. We don't want to do + * this; we are not telnet. There are bad interactions between + * persistent connections and Nagle's algorithm that have very severe + * performance penalties. (Failing to disable Nagle is not much of a + * problem with simple HTTP.) + * + * In spite of these problems, failure here is not a shooting offense. + */ + apr_status_t status = apr_socket_opt_set(s, APR_TCP_NODELAY, 1); + + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, status, ap_server_conf, APLOGNO(00542) + "apr_socket_opt_set: (TCP_NODELAY)"); + } +} +#endif + +#ifdef HAVE_GETPWNAM +AP_DECLARE(uid_t) ap_uname2id(const char *name) +{ + struct passwd *ent; + + if (name[0] == '#') + return (atoi(&name[1])); + + if (!(ent = getpwnam(name))) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00543) + "%s: bad user name %s", ap_server_argv0, name); + exit(1); + } + + return (ent->pw_uid); +} +#endif + +#ifdef HAVE_GETGRNAM +AP_DECLARE(gid_t) ap_gname2id(const char *name) +{ + struct group *ent; + + if (name[0] == '#') + return (atoi(&name[1])); + + if (!(ent = getgrnam(name))) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00544) + "%s: bad group name %s", ap_server_argv0, name); + exit(1); + } + + return (ent->gr_gid); +} +#endif + +#ifndef HAVE_INITGROUPS +int initgroups(const char *name, gid_t basegid) +{ +#if defined(_OSD_POSIX) || defined(OS2) || defined(WIN32) || defined(NETWARE) + return 0; +#else + gid_t groups[NGROUPS_MAX]; + struct group *g; + int index = 0; + + setgrent(); + + groups[index++] = basegid; + + while (index < NGROUPS_MAX && ((g = getgrent()) != NULL)) { + if (g->gr_gid != basegid) { + char **names; + + for (names = g->gr_mem; *names != NULL; ++names) { + if (!strcmp(*names, name)) + groups[index++] = g->gr_gid; + } + } + } + + endgrent(); + + return setgroups(index, groups); +#endif +} +#endif /* def HAVE_INITGROUPS */ + +/* standard mpm configuration handling */ + +const char *ap_mpm_set_pidfile(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + if (cmd->server->is_virtual) { + return "PidFile directive not allowed in <VirtualHost>"; + } + + ap_pid_fname = arg; + return NULL; +} + +void ap_mpm_dump_pidfile(apr_pool_t *p, apr_file_t *out) +{ + apr_file_printf(out, "PidFile: \"%s\"\n", + ap_server_root_relative(p, ap_pid_fname)); +} + +const char *ap_mpm_set_max_requests(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + if (!strcasecmp(cmd->cmd->name, "MaxRequestsPerChild")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00545) + "MaxRequestsPerChild is deprecated, use " + "MaxConnectionsPerChild instead."); + } + + ap_max_requests_per_child = atoi(arg); + + return NULL; +} + +const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy, + const char *arg) +{ + apr_finfo_t finfo; + const char *fname; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + fname = ap_server_root_relative(cmd->temp_pool, arg); + if (!fname) { + return apr_pstrcat(cmd->pool, "Invalid CoreDumpDirectory path ", + arg, NULL); + } + if (apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool) != APR_SUCCESS) { + return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, + " does not exist", NULL); + } + if (finfo.filetype != APR_DIR) { + return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, + " is not a directory", NULL); + } + apr_cpystrn(ap_coredump_dir, fname, sizeof(ap_coredump_dir)); + ap_coredumpdir_configured = 1; + return NULL; +} + +AP_DECLARE(const char *)ap_mpm_set_graceful_shutdown(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + ap_graceful_shutdown_timeout = atoi(arg); + return NULL; +} + +const char *ap_mpm_set_max_mem_free(cmd_parms *cmd, void *dummy, + const char *arg) +{ + long value; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + errno = 0; + value = strtol(arg, NULL, 10); + if (value < 0 || errno == ERANGE) + return apr_pstrcat(cmd->pool, "Invalid MaxMemFree value: ", + arg, NULL); + + ap_max_mem_free = (apr_uint32_t)value * 1024; + + return NULL; +} + +const char *ap_mpm_set_thread_stacksize(cmd_parms *cmd, void *dummy, + const char *arg) +{ + long value; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + errno = 0; + value = strtol(arg, NULL, 10); + if (value < 0 || errno == ERANGE) + return apr_pstrcat(cmd->pool, "Invalid ThreadStackSize value: ", + arg, NULL); + + ap_thread_stacksize = (apr_size_t)value; + + return NULL; +} + +AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +{ + apr_status_t rv; + + if (ap_run_mpm_query(query_code, result, &rv) == DECLINED) { + rv = APR_EGENERAL; + } + + return rv; +} + +static void end_gen(mpm_gen_info_t *gi) +{ + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "end of generation %d", gi->gen); + ap_run_end_generation(ap_server_conf, gi->gen); + APR_RING_REMOVE(gi, link); + APR_RING_INSERT_HEAD(unused_geninfo, gi, mpm_gen_info_t, link); +} + +apr_status_t ap_mpm_end_gen_helper(void *unused) /* cleanup on pconf */ +{ + int gen = ap_config_generation - 1; /* differs from MPM generation */ + mpm_gen_info_t *cur; + + if (geninfo == NULL) { + /* initial pconf teardown, MPM hasn't run */ + return APR_SUCCESS; + } + + cur = APR_RING_FIRST(geninfo); + while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && + cur->gen != gen) { + cur = APR_RING_NEXT(cur, link); + } + + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + /* last child of generation already exited */ + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "no record of generation %d", gen); + } + else { + cur->done = 1; + if (cur->active == 0) { + end_gen(cur); + } + } + + return APR_SUCCESS; +} + +/* core's child-status hook + * tracks number of remaining children per generation and + * runs the end-generation hook when the last child of + * a generation exits + */ +void ap_core_child_status(server_rec *s, pid_t pid, + ap_generation_t gen, int slot, + mpm_child_status status) +{ + mpm_gen_info_t *cur; + const char *status_msg = "unknown status"; + + if (!gen_head_init) { /* where to run this? */ + gen_head_init = 1; + geninfo = apr_pcalloc(s->process->pool, sizeof *geninfo); + unused_geninfo = apr_pcalloc(s->process->pool, sizeof *unused_geninfo); + APR_RING_INIT(geninfo, mpm_gen_info_t, link); + APR_RING_INIT(unused_geninfo, mpm_gen_info_t, link); + } + + cur = APR_RING_FIRST(geninfo); + while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && + cur->gen != gen) { + cur = APR_RING_NEXT(cur, link); + } + + switch(status) { + case MPM_CHILD_STARTED: + status_msg = "started"; + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + /* first child for this generation */ + if (!APR_RING_EMPTY(unused_geninfo, mpm_gen_info_t, link)) { + cur = APR_RING_FIRST(unused_geninfo); + APR_RING_REMOVE(cur, link); + cur->active = cur->done = 0; + } + else { + cur = apr_pcalloc(s->process->pool, sizeof *cur); + } + cur->gen = gen; + APR_RING_ELEM_INIT(cur, link); + APR_RING_INSERT_HEAD(geninfo, cur, mpm_gen_info_t, link); + } + ap_random_parent_after_fork(); + ++cur->active; + break; + case MPM_CHILD_EXITED: + ap_update_global_status(); + status_msg = "exited"; + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00546) + "no record of generation %d of exiting child %" APR_PID_T_FMT, + gen, pid); + } + else { + --cur->active; + if (!cur->active && cur->done) { /* no children, server has stopped/restarted */ + end_gen(cur); + } + } + break; + case MPM_CHILD_LOST_SLOT: + status_msg = "lost slot"; + /* we don't track by slot, so it doesn't matter */ + break; + } + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s, + "mpm child %" APR_PID_T_FMT " (gen %d/slot %d) %s", + pid, gen, slot, status_msg); +} + +AP_DECLARE(apr_status_t) ap_mpm_register_timed_callback(apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton) +{ + return ap_run_mpm_register_timed_callback(t, cbfn, baton); +} + +AP_DECLARE(const char *)ap_show_mpm(void) +{ + const char *name = ap_run_mpm_get_name(); + + if (!name) { + name = ""; + } + + return name; +} + +AP_DECLARE(const char *)ap_check_mpm(void) +{ + static const char *last_mpm_name = NULL; + + if (!_hooks.link_mpm || _hooks.link_mpm->nelts == 0) + return "No MPM loaded."; + else if (_hooks.link_mpm->nelts > 1) + return "More than one MPM loaded."; + + if (last_mpm_name) { + if (strcmp(last_mpm_name, ap_show_mpm())) { + return "The MPM cannot be changed during restart."; + } + } + else { + last_mpm_name = apr_pstrdup(ap_pglobal, ap_show_mpm()); + } + + return NULL; +} diff --git a/server/mpm_fdqueue.c b/server/mpm_fdqueue.c new file mode 100644 index 0000000..3697ca7 --- /dev/null +++ b/server/mpm_fdqueue.c @@ -0,0 +1,534 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mpm_fdqueue.h" + +#if APR_HAS_THREADS + +#include <apr_atomic.h> + +static const apr_uint32_t zero_pt = APR_UINT32_MAX/2; + +struct recycled_pool +{ + apr_pool_t *pool; + struct recycled_pool *next; +}; + +struct fd_queue_info_t +{ + apr_uint32_t volatile idlers; /** + * >= zero_pt: number of idle worker threads + * < zero_pt: number of threads blocked, + * waiting for an idle worker + */ + apr_thread_mutex_t *idlers_mutex; + apr_thread_cond_t *wait_for_idler; + int terminated; + int max_idlers; + int max_recycled_pools; + apr_uint32_t recycled_pools_count; + struct recycled_pool *volatile recycled_pools; +}; + +struct fd_queue_elem_t +{ + apr_socket_t *sd; + void *sd_baton; + apr_pool_t *p; +}; + +static apr_status_t queue_info_cleanup(void *data_) +{ + fd_queue_info_t *qi = data_; + apr_thread_cond_destroy(qi->wait_for_idler); + apr_thread_mutex_destroy(qi->idlers_mutex); + + /* Clean up any pools in the recycled list */ + for (;;) { + struct recycled_pool *first_pool = qi->recycled_pools; + if (first_pool == NULL) { + break; + } + if (apr_atomic_casptr((void *)&qi->recycled_pools, first_pool->next, + first_pool) == first_pool) { + apr_pool_destroy(first_pool->pool); + } + } + + return APR_SUCCESS; +} + +apr_status_t ap_queue_info_create(fd_queue_info_t **queue_info, + apr_pool_t *pool, int max_idlers, + int max_recycled_pools) +{ + apr_status_t rv; + fd_queue_info_t *qi; + + qi = apr_pcalloc(pool, sizeof(*qi)); + + rv = apr_thread_mutex_create(&qi->idlers_mutex, APR_THREAD_MUTEX_DEFAULT, + pool); + if (rv != APR_SUCCESS) { + return rv; + } + rv = apr_thread_cond_create(&qi->wait_for_idler, pool); + if (rv != APR_SUCCESS) { + return rv; + } + qi->recycled_pools = NULL; + qi->max_recycled_pools = max_recycled_pools; + qi->max_idlers = max_idlers; + qi->idlers = zero_pt; + apr_pool_cleanup_register(pool, qi, queue_info_cleanup, + apr_pool_cleanup_null); + + *queue_info = qi; + + return APR_SUCCESS; +} + +apr_status_t ap_queue_info_set_idle(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle) +{ + apr_status_t rv; + + ap_queue_info_push_pool(queue_info, pool_to_recycle); + + /* If other threads are waiting on a worker, wake one up */ + if (apr_atomic_inc32(&queue_info->idlers) < zero_pt) { + rv = apr_thread_mutex_lock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + AP_DEBUG_ASSERT(0); + return rv; + } + rv = apr_thread_cond_signal(queue_info->wait_for_idler); + if (rv != APR_SUCCESS) { + apr_thread_mutex_unlock(queue_info->idlers_mutex); + return rv; + } + rv = apr_thread_mutex_unlock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + } + + return APR_SUCCESS; +} + +apr_status_t ap_queue_info_try_get_idler(fd_queue_info_t *queue_info) +{ + /* Don't block if there isn't any idle worker. */ + for (;;) { + apr_uint32_t idlers = queue_info->idlers; + if (idlers <= zero_pt) { + return APR_EAGAIN; + } + if (apr_atomic_cas32(&queue_info->idlers, idlers - 1, + idlers) == idlers) { + return APR_SUCCESS; + } + } +} + +apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t *queue_info, + int *had_to_block) +{ + apr_status_t rv; + + /* Block if there isn't any idle worker. + * apr_atomic_add32(x, -1) does the same as dec32(x), except + * that it returns the previous value (unlike dec32's bool). + */ + if (apr_atomic_add32(&queue_info->idlers, -1) <= zero_pt) { + rv = apr_thread_mutex_lock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + AP_DEBUG_ASSERT(0); + apr_atomic_inc32(&(queue_info->idlers)); /* back out dec */ + return rv; + } + /* Re-check the idle worker count to guard against a + * race condition. Now that we're in the mutex-protected + * region, one of two things may have happened: + * - If the idle worker count is still negative, the + * workers are all still busy, so it's safe to + * block on a condition variable. + * - If the idle worker count is non-negative, then a + * worker has become idle since the first check + * of queue_info->idlers above. It's possible + * that the worker has also signaled the condition + * variable--and if so, the listener missed it + * because it wasn't yet blocked on the condition + * variable. But if the idle worker count is + * now non-negative, it's safe for this function to + * return immediately. + * + * A "negative value" (relative to zero_pt) in + * queue_info->idlers tells how many + * threads are waiting on an idle worker. + */ + if (queue_info->idlers < zero_pt) { + if (had_to_block) { + *had_to_block = 1; + } + rv = apr_thread_cond_wait(queue_info->wait_for_idler, + queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + AP_DEBUG_ASSERT(0); + apr_thread_mutex_unlock(queue_info->idlers_mutex); + return rv; + } + } + rv = apr_thread_mutex_unlock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + } + + if (queue_info->terminated) { + return APR_EOF; + } + else { + return APR_SUCCESS; + } +} + +apr_uint32_t ap_queue_info_num_idlers(fd_queue_info_t *queue_info) +{ + apr_uint32_t val; + val = apr_atomic_read32(&queue_info->idlers); + return (val > zero_pt) ? val - zero_pt : 0; +} + +void ap_queue_info_push_pool(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle) +{ + struct recycled_pool *new_recycle; + /* If we have been given a pool to recycle, atomically link + * it into the queue_info's list of recycled pools + */ + if (!pool_to_recycle) + return; + + if (queue_info->max_recycled_pools >= 0) { + apr_uint32_t n = apr_atomic_read32(&queue_info->recycled_pools_count); + if (n >= queue_info->max_recycled_pools) { + apr_pool_destroy(pool_to_recycle); + return; + } + apr_atomic_inc32(&queue_info->recycled_pools_count); + } + + apr_pool_clear(pool_to_recycle); + new_recycle = apr_palloc(pool_to_recycle, sizeof *new_recycle); + new_recycle->pool = pool_to_recycle; + for (;;) { + /* + * Save queue_info->recycled_pool in local variable next because + * new_recycle->next can be changed after apr_atomic_casptr + * function call. For gory details see PR 44402. + */ + struct recycled_pool *next = queue_info->recycled_pools; + new_recycle->next = next; + if (apr_atomic_casptr((void *)&queue_info->recycled_pools, + new_recycle, next) == next) + break; + } +} + +void ap_queue_info_pop_pool(fd_queue_info_t *queue_info, + apr_pool_t **recycled_pool) +{ + /* Atomically pop a pool from the recycled list */ + + /* This function is safe only as long as it is single threaded because + * it reaches into the queue and accesses "next" which can change. + * We are OK today because it is only called from the listener thread. + * cas-based pushes do not have the same limitation - any number can + * happen concurrently with a single cas-based pop. + */ + + *recycled_pool = NULL; + + + /* Atomically pop a pool from the recycled list */ + for (;;) { + struct recycled_pool *first_pool = queue_info->recycled_pools; + if (first_pool == NULL) { + break; + } + if (apr_atomic_casptr((void *)&queue_info->recycled_pools, + first_pool->next, first_pool) == first_pool) { + *recycled_pool = first_pool->pool; + if (queue_info->max_recycled_pools >= 0) + apr_atomic_dec32(&queue_info->recycled_pools_count); + break; + } + } +} + +void ap_queue_info_free_idle_pools(fd_queue_info_t *queue_info) +{ + apr_pool_t *p; + + queue_info->max_recycled_pools = 0; + for (;;) { + ap_queue_info_pop_pool(queue_info, &p); + if (p == NULL) + break; + apr_pool_destroy(p); + } + apr_atomic_set32(&queue_info->recycled_pools_count, 0); +} + + +apr_status_t ap_queue_info_term(fd_queue_info_t *queue_info) +{ + apr_status_t rv; + + rv = apr_thread_mutex_lock(queue_info->idlers_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + + queue_info->terminated = 1; + apr_thread_cond_broadcast(queue_info->wait_for_idler); + + return apr_thread_mutex_unlock(queue_info->idlers_mutex); +} + +/** + * Detects when the fd_queue_t is full. This utility function is expected + * to be called from within critical sections, and is not threadsafe. + */ +#define ap_queue_full(queue) ((queue)->nelts == (queue)->bounds) + +/** + * Detects when the fd_queue_t is empty. This utility function is expected + * to be called from within critical sections, and is not threadsafe. + */ +#define ap_queue_empty(queue) ((queue)->nelts == 0 && \ + APR_RING_EMPTY(&queue->timers, \ + timer_event_t, link)) + +/** + * Callback routine that is called to destroy this + * fd_queue_t when its pool is destroyed. + */ +static apr_status_t ap_queue_destroy(void *data) +{ + fd_queue_t *queue = data; + + /* Ignore errors here, we can't do anything about them anyway. + * XXX: We should at least try to signal an error here, it is + * indicative of a programmer error. -aaron */ + apr_thread_cond_destroy(queue->not_empty); + apr_thread_mutex_destroy(queue->one_big_mutex); + + return APR_SUCCESS; +} + +/** + * Initialize the fd_queue_t. + */ +apr_status_t ap_queue_create(fd_queue_t **pqueue, int capacity, apr_pool_t *p) +{ + apr_status_t rv; + fd_queue_t *queue; + + queue = apr_pcalloc(p, sizeof *queue); + + if ((rv = apr_thread_mutex_create(&queue->one_big_mutex, + APR_THREAD_MUTEX_DEFAULT, + p)) != APR_SUCCESS) { + return rv; + } + if ((rv = apr_thread_cond_create(&queue->not_empty, p)) != APR_SUCCESS) { + return rv; + } + + APR_RING_INIT(&queue->timers, timer_event_t, link); + + queue->data = apr_pcalloc(p, capacity * sizeof(fd_queue_elem_t)); + queue->bounds = capacity; + + apr_pool_cleanup_register(p, queue, ap_queue_destroy, + apr_pool_cleanup_null); + *pqueue = queue; + + return APR_SUCCESS; +} + +/** + * Push a new socket onto the queue. + * + * precondition: ap_queue_info_wait_for_idler has already been called + * to reserve an idle worker thread + */ +apr_status_t ap_queue_push_socket(fd_queue_t *queue, + apr_socket_t *sd, void *sd_baton, + apr_pool_t *p) +{ + fd_queue_elem_t *elem; + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + AP_DEBUG_ASSERT(!queue->terminated); + AP_DEBUG_ASSERT(!ap_queue_full(queue)); + + elem = &queue->data[queue->in++]; + if (queue->in >= queue->bounds) + queue->in -= queue->bounds; + elem->sd = sd; + elem->sd_baton = sd_baton; + elem->p = p; + queue->nelts++; + + apr_thread_cond_signal(queue->not_empty); + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +apr_status_t ap_queue_push_timer(fd_queue_t *queue, timer_event_t *te) +{ + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + AP_DEBUG_ASSERT(!queue->terminated); + + APR_RING_INSERT_TAIL(&queue->timers, te, timer_event_t, link); + + apr_thread_cond_signal(queue->not_empty); + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +/** + * Retrieves the next available socket from the queue. If there are no + * sockets available, it will block until one becomes available. + * Once retrieved, the socket is placed into the address specified by + * 'sd'. + */ +apr_status_t ap_queue_pop_something(fd_queue_t *queue, + apr_socket_t **sd, void **sd_baton, + apr_pool_t **p, timer_event_t **te_out) +{ + fd_queue_elem_t *elem; + timer_event_t *te; + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + /* Keep waiting until we wake up and find that the queue is not empty. */ + if (ap_queue_empty(queue)) { + if (!queue->terminated) { + apr_thread_cond_wait(queue->not_empty, queue->one_big_mutex); + } + /* If we wake up and it's still empty, then we were interrupted */ + if (ap_queue_empty(queue)) { + rv = apr_thread_mutex_unlock(queue->one_big_mutex); + if (rv != APR_SUCCESS) { + return rv; + } + if (queue->terminated) { + return APR_EOF; /* no more elements ever again */ + } + else { + return APR_EINTR; + } + } + } + + te = NULL; + if (te_out) { + if (!APR_RING_EMPTY(&queue->timers, timer_event_t, link)) { + te = APR_RING_FIRST(&queue->timers); + APR_RING_REMOVE(te, link); + } + *te_out = te; + } + if (!te) { + elem = &queue->data[queue->out++]; + if (queue->out >= queue->bounds) + queue->out -= queue->bounds; + queue->nelts--; + + *sd = elem->sd; + if (sd_baton) { + *sd_baton = elem->sd_baton; + } + *p = elem->p; +#ifdef AP_DEBUG + elem->sd = NULL; + elem->p = NULL; +#endif /* AP_DEBUG */ + } + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +static apr_status_t queue_interrupt(fd_queue_t *queue, int all, int term) +{ + apr_status_t rv; + + if (queue->terminated) { + return APR_EOF; + } + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + /* we must hold one_big_mutex when setting this... otherwise, + * we could end up setting it and waking everybody up just after a + * would-be popper checks it but right before they block + */ + if (term) { + queue->terminated = 1; + } + if (all) + apr_thread_cond_broadcast(queue->not_empty); + else + apr_thread_cond_signal(queue->not_empty); + + return apr_thread_mutex_unlock(queue->one_big_mutex); +} + +apr_status_t ap_queue_interrupt_all(fd_queue_t *queue) +{ + return queue_interrupt(queue, 1, 0); +} + +apr_status_t ap_queue_interrupt_one(fd_queue_t *queue) +{ + return queue_interrupt(queue, 0, 0); +} + +apr_status_t ap_queue_term(fd_queue_t *queue) +{ + return queue_interrupt(queue, 1, 1); +} + +#endif /* APR_HAS_THREADS */ diff --git a/server/mpm_fdqueue.h b/server/mpm_fdqueue.h new file mode 100644 index 0000000..1047f88 --- /dev/null +++ b/server/mpm_fdqueue.h @@ -0,0 +1,110 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file server/mpm_fdqueue.h + * @brief fd queue declarations + * + * @addtogroup APACHE_MPM_EVENT + * @{ + */ + +#ifndef MPM_FDQUEUE_H +#define MPM_FDQUEUE_H + +#include <apr.h> + +/* This code is not AP_DECLARE()ed/exported, and used by MPMs event/worker + * only (for now), not worth thinking about w/o threads either... + */ +#if APR_HAS_THREADS + +#include "ap_mpm.h" + +#include <apr_ring.h> +#include <apr_pools.h> +#include <apr_thread_mutex.h> +#include <apr_thread_cond.h> +#include <apr_network_io.h> + +struct fd_queue_info_t; /* opaque */ +struct fd_queue_elem_t; /* opaque */ +typedef struct fd_queue_info_t fd_queue_info_t; +typedef struct fd_queue_elem_t fd_queue_elem_t; + +AP_DECLARE(apr_status_t) ap_queue_info_create(fd_queue_info_t **queue_info, + apr_pool_t *pool, int max_idlers, + int max_recycled_pools); +AP_DECLARE(apr_status_t) ap_queue_info_set_idle(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle); +AP_DECLARE(apr_status_t) ap_queue_info_try_get_idler(fd_queue_info_t *queue_info); +AP_DECLARE(apr_status_t) ap_queue_info_wait_for_idler(fd_queue_info_t *queue_info, + int *had_to_block); +AP_DECLARE(apr_uint32_t) ap_queue_info_num_idlers(fd_queue_info_t *queue_info); +AP_DECLARE(apr_status_t) ap_queue_info_term(fd_queue_info_t *queue_info); + +AP_DECLARE(void) ap_queue_info_pop_pool(fd_queue_info_t *queue_info, + apr_pool_t **recycled_pool); +AP_DECLARE(void) ap_queue_info_push_pool(fd_queue_info_t *queue_info, + apr_pool_t *pool_to_recycle); +AP_DECLARE(void) ap_queue_info_free_idle_pools(fd_queue_info_t *queue_info); + +struct timer_event_t +{ + APR_RING_ENTRY(timer_event_t) link; + apr_time_t when; + ap_mpm_callback_fn_t *cbfunc; + void *baton; + int canceled; + apr_array_header_t *remove; +}; +typedef struct timer_event_t timer_event_t; + +struct fd_queue_t +{ + APR_RING_HEAD(timers_t, timer_event_t) timers; + fd_queue_elem_t *data; + unsigned int nelts; + unsigned int bounds; + unsigned int in; + unsigned int out; + apr_thread_mutex_t *one_big_mutex; + apr_thread_cond_t *not_empty; + volatile int terminated; +}; +typedef struct fd_queue_t fd_queue_t; + +AP_DECLARE(apr_status_t) ap_queue_create(fd_queue_t **pqueue, + int capacity, apr_pool_t *p); +AP_DECLARE(apr_status_t) ap_queue_push_socket(fd_queue_t *queue, + apr_socket_t *sd, void *sd_baton, + apr_pool_t *p); +AP_DECLARE(apr_status_t) ap_queue_push_timer(fd_queue_t *queue, + timer_event_t *te); +AP_DECLARE(apr_status_t) ap_queue_pop_something(fd_queue_t *queue, + apr_socket_t **sd, void **sd_baton, + apr_pool_t **p, timer_event_t **te); +#define ap_queue_pop_socket(q_, s_, p_) \ + ap_queue_pop_something((q_), (s_), NULL, (p_), NULL) + +AP_DECLARE(apr_status_t) ap_queue_interrupt_all(fd_queue_t *queue); +AP_DECLARE(apr_status_t) ap_queue_interrupt_one(fd_queue_t *queue); +AP_DECLARE(apr_status_t) ap_queue_term(fd_queue_t *queue); + +#endif /* APR_HAS_THREADS */ + +#endif /* MPM_FDQUEUE_H */ +/** @} */ diff --git a/server/mpm_unix.c b/server/mpm_unix.c new file mode 100644 index 0000000..8c4d233 --- /dev/null +++ b/server/mpm_unix.c @@ -0,0 +1,1108 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* The purpose of this file is to store the code that MOST mpm's will need + * this does not mean a function only goes into this file if every MPM needs + * it. It means that if a function is needed by more than one MPM, and + * future maintenance would be served by making the code common, then the + * function belongs here. + * + * This is going in src/main because it is not platform specific, it is + * specific to multi-process servers, but NOT to Unix. Which is why it + * does not belong in src/os/unix + */ + +#ifndef WIN32 + +#include "apr.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_strings.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_getopt.h" +#include "apr_optional.h" +#include "apr_allocator.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "http_main.h" +#include "mpm_common.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "util_mutex.h" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +typedef enum { + DO_NOTHING, + SEND_SIGTERM, + SEND_SIGTERM_NOLOG, + SEND_SIGKILL, + GIVEUP +} action_t; + +typedef struct extra_process_t { + struct extra_process_t *next; + pid_t pid; + ap_generation_t gen; +} extra_process_t; + +static extra_process_t *extras; + +AP_DECLARE(void) ap_register_extra_mpm_process(pid_t pid, ap_generation_t gen) +{ + extra_process_t *p = (extra_process_t *)ap_malloc(sizeof(extra_process_t)); + + p->next = extras; + p->pid = pid; + p->gen = gen; + extras = p; +} + +AP_DECLARE(int) ap_unregister_extra_mpm_process(pid_t pid, ap_generation_t *old_gen) +{ + extra_process_t *cur = extras; + extra_process_t *prev = NULL; + + while (cur && cur->pid != pid) { + prev = cur; + cur = cur->next; + } + + if (cur) { + if (prev) { + prev->next = cur->next; + } + else { + extras = cur->next; + } + *old_gen = cur->gen; + free(cur); + return 1; /* found */ + } + else { + /* we don't know about any such process */ + return 0; + } +} + +static int reclaim_one_pid(pid_t pid, action_t action) +{ + apr_proc_t proc; + apr_status_t waitret; + apr_exit_why_e why; + int status; + + /* Ensure pid sanity. */ + if (pid < 1) { + return 1; + } + + proc.pid = pid; + waitret = apr_proc_wait(&proc, &status, &why, APR_NOWAIT); + if (waitret != APR_CHILD_NOTDONE) { + if (waitret == APR_CHILD_DONE) + ap_process_child_status(&proc, why, status); + return 1; + } + + switch(action) { + case DO_NOTHING: + break; + + case SEND_SIGTERM: + /* ok, now it's being annoying */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + 0, ap_server_conf, APLOGNO(00045) + "child process %" APR_PID_T_FMT + " still did not exit, " + "sending a SIGTERM", + pid); + /* FALLTHROUGH */ + case SEND_SIGTERM_NOLOG: + kill(pid, SIGTERM); + break; + + case SEND_SIGKILL: + ap_log_error(APLOG_MARK, APLOG_ERR, + 0, ap_server_conf, APLOGNO(00046) + "child process %" APR_PID_T_FMT + " still did not exit, " + "sending a SIGKILL", + pid); + kill(pid, SIGKILL); + break; + + case GIVEUP: + /* gave it our best shot, but alas... If this really + * is a child we are trying to kill and it really hasn't + * exited, we will likely fail to bind to the port + * after the restart. + */ + ap_log_error(APLOG_MARK, APLOG_ERR, + 0, ap_server_conf, APLOGNO(00047) + "could not make child process %" APR_PID_T_FMT + " exit, " + "attempting to continue anyway", + pid); + break; + } + + return 0; +} + +AP_DECLARE(void) ap_reclaim_child_processes(int terminate, + ap_reclaim_callback_fn_t *mpm_callback) +{ + apr_time_t waittime = 1024 * 16; + int i; + extra_process_t *cur_extra; + int not_dead_yet; + int max_daemons; + apr_time_t starttime = apr_time_now(); + /* this table of actions and elapsed times tells what action is taken + * at which elapsed time from starting the reclaim + */ + struct { + action_t action; + apr_time_t action_time; + } action_table[] = { + {DO_NOTHING, 0}, /* dummy entry for iterations where we reap + * children but take no action against + * stragglers + */ + {SEND_SIGTERM_NOLOG, 0}, /* skipped if terminate == 0 */ + {SEND_SIGTERM, apr_time_from_sec(3)}, + {SEND_SIGTERM, apr_time_from_sec(5)}, + {SEND_SIGTERM, apr_time_from_sec(7)}, + {SEND_SIGKILL, apr_time_from_sec(9)}, + {GIVEUP, apr_time_from_sec(10)} + }; + int cur_action; /* index of action we decided to take this + * iteration + */ + int next_action = terminate ? 1 : 2; /* index of first real action */ + + ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons); + + do { + if (action_table[next_action].action_time > 0) { + apr_sleep(waittime); + /* don't let waittime get longer than 1 second; otherwise, we don't + * react quickly to the last child exiting, and taking action can + * be delayed + */ + waittime = waittime * 4; + if (waittime > apr_time_from_sec(1)) { + waittime = apr_time_from_sec(1); + } + } + + /* see what action to take, if any */ + if (action_table[next_action].action_time <= apr_time_now() - starttime) { + cur_action = next_action; + ++next_action; + } + else { + cur_action = 0; /* nothing to do */ + } + + /* now see who is done */ + not_dead_yet = 0; + for (i = 0; i < max_daemons; ++i) { + process_score *ps = ap_get_scoreboard_process(i); + pid_t pid = ps->pid; + + if (pid == 0) { + continue; /* not every scoreboard entry is in use */ + } + + if (reclaim_one_pid(pid, action_table[cur_action].action)) { + mpm_callback(i, 0, 0); + } + else { + ++not_dead_yet; + } + } + + cur_extra = extras; + while (cur_extra) { + ap_generation_t old_gen; + extra_process_t *next = cur_extra->next; + + if (reclaim_one_pid(cur_extra->pid, action_table[cur_action].action)) { + if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { + mpm_callback(-1, cur_extra->pid, old_gen); + } + else { + AP_DEBUG_ASSERT(1 == 0); + } + } + else { + ++not_dead_yet; + } + cur_extra = next; + } +#if APR_HAS_OTHER_CHILD + apr_proc_other_child_refresh_all(APR_OC_REASON_RESTART); +#endif + + } while (not_dead_yet > 0 && + action_table[cur_action].action != GIVEUP); +} + +AP_DECLARE(void) ap_relieve_child_processes(ap_reclaim_callback_fn_t *mpm_callback) +{ + int i; + extra_process_t *cur_extra; + int max_daemons; + + ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons); + + /* now see who is done */ + for (i = 0; i < max_daemons; ++i) { + process_score *ps = ap_get_scoreboard_process(i); + pid_t pid = ps->pid; + + if (pid == 0) { + continue; /* not every scoreboard entry is in use */ + } + + if (reclaim_one_pid(pid, DO_NOTHING)) { + mpm_callback(i, 0, 0); + } + } + + cur_extra = extras; + while (cur_extra) { + ap_generation_t old_gen; + extra_process_t *next = cur_extra->next; + + if (reclaim_one_pid(cur_extra->pid, DO_NOTHING)) { + if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { + mpm_callback(-1, cur_extra->pid, old_gen); + } + else { + AP_DEBUG_ASSERT(1 == 0); + } + } + cur_extra = next; + } +} + +/* Before sending the signal to the pid this function verifies that + * the pid is a member of the current process group; either using + * apr_proc_wait(), where waitpid() guarantees to fail for non-child + * processes; or by using getpgid() directly, if available. */ +AP_DECLARE(apr_status_t) ap_mpm_safe_kill(pid_t pid, int sig) +{ +#ifndef HAVE_GETPGID + apr_proc_t proc; + apr_status_t rv; + apr_exit_why_e why; + int status; + + /* Ensure pid sanity */ + if (pid < 1) { + return APR_EINVAL; + } + + proc.pid = pid; + rv = apr_proc_wait(&proc, &status, &why, APR_NOWAIT); + if (rv == APR_CHILD_DONE) { + /* The child already died - log the termination status if + * necessary: */ + ap_process_child_status(&proc, why, status); + return APR_EINVAL; + } + else if (rv != APR_CHILD_NOTDONE) { + /* The child is already dead and reaped, or was a bogus pid - + * log this either way. */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00048) + "cannot send signal %d to pid %ld (non-child or " + "already dead)", sig, (long)pid); + return APR_EINVAL; + } +#else + pid_t pg; + + /* Ensure pid sanity. */ + if (pid < 1) { + return APR_EINVAL; + } + + pg = getpgid(pid); + if (pg == -1) { + /* Process already dead... */ + return errno; + } + + if (pg != getpgrp()) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, ap_server_conf, APLOGNO(00049) + "refusing to send signal %d to pid %ld outside " + "process group", sig, (long)pid); + return APR_EINVAL; + } +#endif + + return kill(pid, sig) ? errno : APR_SUCCESS; +} + + +AP_DECLARE(int) ap_process_child_status(apr_proc_t *pid, apr_exit_why_e why, + int status) +{ + int signum = status; + const char *sigdesc; + + /* Child died... if it died due to a fatal error, + * we should simply bail out. The caller needs to + * check for bad rc from us and exit, running any + * appropriate cleanups. + * + * If the child died due to a resource shortage, + * the parent should limit the rate of forking + */ + if (APR_PROC_CHECK_EXIT(why)) { + if (status == APEXIT_CHILDSICK) { + return status; + } + + if (status == APEXIT_CHILDFATAL) { + ap_log_error(APLOG_MARK, APLOG_ALERT, + 0, ap_server_conf, APLOGNO(00050) + "Child %" APR_PID_T_FMT + " returned a Fatal error... Apache is exiting!", + pid->pid); + return APEXIT_CHILDFATAL; + } + + return 0; + } + + if (APR_PROC_CHECK_SIGNALED(why)) { + sigdesc = apr_signal_description_get(signum); + + switch (signum) { + case SIGTERM: + case SIGHUP: + case AP_SIG_GRACEFUL: + case SIGKILL: + break; + + default: + if (APR_PROC_CHECK_CORE_DUMP(why)) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, + 0, ap_server_conf, APLOGNO(00051) + "child pid %ld exit signal %s (%d), " + "possible coredump in %s", + (long)pid->pid, sigdesc, signum, + ap_coredump_dir); + } + else { + ap_log_error(APLOG_MARK, APLOG_NOTICE, + 0, ap_server_conf, APLOGNO(00052) + "child pid %ld exit signal %s (%d)", + (long)pid->pid, sigdesc, signum); + } + } + } + return 0; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod) +{ + apr_status_t rv; + + *pod = apr_palloc(p, sizeof(**pod)); + rv = apr_file_pipe_create_ex(&((*pod)->pod_in), &((*pod)->pod_out), + APR_WRITE_BLOCK, p); + if (rv != APR_SUCCESS) { + return rv; + } + + apr_file_pipe_timeout_set((*pod)->pod_in, 0); + (*pod)->p = p; + + /* close these before exec. */ + apr_file_inherit_unset((*pod)->pod_in); + apr_file_inherit_unset((*pod)->pod_out); + + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_check(ap_pod_t *pod) +{ + char c; + apr_size_t len = 1; + apr_status_t rv; + + rv = apr_file_read(pod->pod_in, &c, &len); + + if ((rv == APR_SUCCESS) && (len == 1)) { + return APR_SUCCESS; + } + + if (rv != APR_SUCCESS) { + return rv; + } + + return AP_NORESTART; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod) +{ + apr_status_t rv; + + rv = apr_file_close(pod->pod_out); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = apr_file_close(pod->pod_in); + if (rv != APR_SUCCESS) { + return rv; + } + + return APR_SUCCESS; +} + +static apr_status_t pod_signal_internal(ap_pod_t *pod) +{ + apr_status_t rv; + char char_of_death = '!'; + apr_size_t one = 1; + + rv = apr_file_write(pod->pod_out, &char_of_death, &one); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00053) + "write pipe_of_death"); + } + + return rv; +} + +AP_DECLARE(apr_status_t) ap_mpm_podx_open(apr_pool_t *p, ap_pod_t **pod) +{ + apr_status_t rv; + + *pod = apr_palloc(p, sizeof(**pod)); + rv = apr_file_pipe_create(&((*pod)->pod_in), &((*pod)->pod_out), p); + if (rv != APR_SUCCESS) { + return rv; + } + /* + apr_file_pipe_timeout_set((*pod)->pod_in, 0); + */ + (*pod)->p = p; + + /* close these before exec. */ + apr_file_inherit_unset((*pod)->pod_in); + apr_file_inherit_unset((*pod)->pod_out); + + return APR_SUCCESS; +} + +AP_DECLARE(int) ap_mpm_podx_check(ap_pod_t *pod) +{ + char c; + apr_os_file_t fd; + int rc; + + /* we need to surface EINTR so we'll have to grab the + * native file descriptor and do the OS read() ourselves + */ + apr_os_file_get(&fd, pod->pod_in); + rc = read(fd, &c, 1); + if (rc == 1) { + switch (c) { + case AP_MPM_PODX_RESTART_CHAR: + return AP_MPM_PODX_RESTART; + case AP_MPM_PODX_GRACEFUL_CHAR: + return AP_MPM_PODX_GRACEFUL; + } + } + return AP_MPM_PODX_NORESTART; +} + +AP_DECLARE(apr_status_t) ap_mpm_podx_close(ap_pod_t *pod) +{ + apr_status_t rv; + + rv = apr_file_close(pod->pod_out); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = apr_file_close(pod->pod_in); + if (rv != APR_SUCCESS) { + return rv; + } + return rv; +} + +static apr_status_t podx_signal_internal(ap_pod_t *pod, + ap_podx_restart_t graceful) +{ + apr_status_t rv; + apr_size_t one = 1; + char char_of_death = ' '; + switch (graceful) { + case AP_MPM_PODX_RESTART: + char_of_death = AP_MPM_PODX_RESTART_CHAR; + break; + case AP_MPM_PODX_GRACEFUL: + char_of_death = AP_MPM_PODX_GRACEFUL_CHAR; + break; + case AP_MPM_PODX_NORESTART: + break; + } + + rv = apr_file_write(pod->pod_out, &char_of_death, &one); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(02404) + "write pipe_of_death"); + } + return rv; +} + +AP_DECLARE(apr_status_t) ap_mpm_podx_signal(ap_pod_t * pod, + ap_podx_restart_t graceful) +{ + return podx_signal_internal(pod, graceful); +} + +AP_DECLARE(void) ap_mpm_podx_killpg(ap_pod_t * pod, int num, + ap_podx_restart_t graceful) +{ + int i; + apr_status_t rv = APR_SUCCESS; + + for (i = 0; i < num && rv == APR_SUCCESS; i++) { + rv = podx_signal_internal(pod, graceful); + } +} + +/* This function connects to the server and sends enough data to + * ensure the child wakes up and processes a new connection. This + * permits the MPM to skip the poll when there is only one listening + * socket, because it provides a alternate way to unblock an accept() + * when the pod is used. */ +static apr_status_t dummy_connection(ap_pod_t *pod) +{ + const char *data; + apr_status_t rv; + apr_socket_t *sock; + apr_pool_t *p; + apr_size_t len; + ap_listen_rec *lp; + + /* create a temporary pool for the socket. pconf stays around too long */ + rv = apr_pool_create(&p, pod->p); + if (rv != APR_SUCCESS) { + return rv; + } + apr_pool_tag(p, "dummy_connection"); + + /* If possible, find a listener which is configured for + * plain-HTTP, not SSL; using an SSL port would either be + * expensive to do correctly (performing a complete SSL handshake) + * or cause log spam by doing incorrectly (simply sending EOF). */ + lp = ap_listeners; + while (lp && lp->protocol && ap_cstr_casecmp(lp->protocol, "http") != 0) { + lp = lp->next; + } + if (!lp) { + lp = ap_listeners; + } + + rv = apr_socket_create(&sock, lp->bind_addr->family, SOCK_STREAM, 0, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00054) + "get socket to connect to listener"); + apr_pool_destroy(p); + return rv; + } + + /* on some platforms (e.g., FreeBSD), the kernel won't accept many + * queued connections before it starts blocking local connects... + * we need to keep from blocking too long and instead return an error, + * because the MPM won't want to hold up a graceful restart for a + * long time + */ + rv = apr_socket_timeout_set(sock, apr_time_from_sec(3)); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00055) + "set timeout on socket to connect to listener"); + apr_socket_close(sock); + apr_pool_destroy(p); + return rv; + } + + rv = apr_socket_connect(sock, lp->bind_addr); + if (rv != APR_SUCCESS) { + int log_level = APLOG_WARNING; + + if (APR_STATUS_IS_TIMEUP(rv)) { + /* probably some server processes bailed out already and there + * is nobody around to call accept and clear out the kernel + * connection queue; usually this is not worth logging + */ + log_level = APLOG_DEBUG; + } + + ap_log_error(APLOG_MARK, log_level, rv, ap_server_conf, APLOGNO(00056) + "connect to listener on %pI", lp->bind_addr); + apr_pool_destroy(p); + return rv; + } + + if (lp->protocol && ap_cstr_casecmp(lp->protocol, "https") == 0) { + /* Send a TLS 1.0 close_notify alert. This is perhaps the + * "least wrong" way to open and cleanly terminate an SSL + * connection. It should "work" without noisy error logs if + * the server actually expects SSLv3/TLSv1. With + * SSLv23_server_method() OpenSSL's SSL_accept() fails + * ungracefully on receipt of this message, since it requires + * an 11-byte ClientHello message and this is too short. */ + static const unsigned char tls10_close_notify[7] = { + '\x15', /* TLSPlainText.type = Alert (21) */ + '\x03', '\x01', /* TLSPlainText.version = {3, 1} */ + '\x00', '\x02', /* TLSPlainText.length = 2 */ + '\x01', /* Alert.level = warning (1) */ + '\x00' /* Alert.description = close_notify (0) */ + }; + data = (const char *)tls10_close_notify; + len = sizeof(tls10_close_notify); + } + else /* ... XXX other request types here? */ { + /* Create an HTTP request string. We include a User-Agent so + * that administrators can track down the cause of the + * odd-looking requests in their logs. A complete request is + * used since kernel-level filtering may require that much + * data before returning from accept(). */ + data = apr_pstrcat(p, "OPTIONS * HTTP/1.0\r\nUser-Agent: ", + ap_get_server_description(), + " (internal dummy connection)\r\n\r\n", NULL); + len = strlen(data); + } + + apr_socket_send(sock, data, &len); + apr_socket_close(sock); + apr_pool_destroy(p); + + return rv; +} + +AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod) +{ + apr_status_t rv; + + rv = pod_signal_internal(pod); + if (rv != APR_SUCCESS) { + return rv; + } + + return dummy_connection(pod); +} + +void ap_mpm_pod_killpg(ap_pod_t *pod, int num) +{ + int i; + apr_status_t rv = APR_SUCCESS; + + /* we don't write anything to the pod here... we assume + * that the would-be reader of the pod has another way to + * see that it is time to die once we wake it up + * + * writing lots of things to the pod at once is very + * problematic... we can fill the kernel pipe buffer and + * be blocked until somebody consumes some bytes or + * we hit a timeout... if we hit a timeout we can't just + * keep trying because maybe we'll never successfully + * write again... but then maybe we'll leave would-be + * readers stranded (a number of them could be tied up for + * a while serving time-consuming requests) + */ + /* Recall: we only worry about IDLE child processes here */ + for (i = 0; i < num && rv == APR_SUCCESS; i++) { + if (ap_scoreboard_image->servers[i][0].status != SERVER_READY || + ap_scoreboard_image->servers[i][0].pid == 0) { + continue; + } + rv = dummy_connection(pod); + } +} + +static const char *dash_k_arg = NULL; +static const char *dash_k_arg_noarg = "noarg"; + +static int send_signal(pid_t pid, int sig) +{ + if (kill(pid, sig) < 0) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, errno, NULL, APLOGNO(00057) + "sending signal to server"); + return 1; + } + return 0; +} + +int ap_signal_server(int *exit_status, apr_pool_t *pconf) +{ + apr_status_t rv; + pid_t otherpid; + int running = 0; + const char *status; + + *exit_status = 0; + + rv = ap_read_pid(pconf, ap_pid_fname, &otherpid); + if (rv != APR_SUCCESS) { + if (!APR_STATUS_IS_ENOENT(rv)) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, rv, NULL, APLOGNO(00058) + "Error retrieving pid file %s", ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00059) + "Remove it before continuing if it is corrupted."); + *exit_status = 1; + return 1; + } + status = "httpd (no pid file) not running"; + } + else { + /* With containerization, httpd may get the same PID at each startup, + * handle it as if it were not running (it obviously can't). + */ + if (otherpid != getpid() && kill(otherpid, 0) == 0) { + running = 1; + status = apr_psprintf(pconf, + "httpd (pid %" APR_PID_T_FMT ") already " + "running", otherpid); + } + else { + status = apr_psprintf(pconf, + "httpd (pid %" APR_PID_T_FMT "?) not running", + otherpid); + } + } + + if (!strcmp(dash_k_arg, "start") || dash_k_arg == dash_k_arg_noarg) { + if (running) { + printf("%s\n", status); + return 1; + } + } + + if (!strcmp(dash_k_arg, "stop")) { + if (!running) { + printf("%s\n", status); + } + else { + send_signal(otherpid, SIGTERM); + } + return 1; + } + + if (!strcmp(dash_k_arg, "restart")) { + if (!running) { + printf("httpd not running, trying to start\n"); + } + else { + *exit_status = send_signal(otherpid, SIGHUP); + return 1; + } + } + + if (!strcmp(dash_k_arg, "graceful")) { + if (!running) { + printf("httpd not running, trying to start\n"); + } + else { + *exit_status = send_signal(otherpid, AP_SIG_GRACEFUL); + return 1; + } + } + + if (!strcmp(dash_k_arg, "graceful-stop")) { + if (!running) { + printf("%s\n", status); + } + else { + *exit_status = send_signal(otherpid, AP_SIG_GRACEFUL_STOP); + } + return 1; + } + + return 0; +} + +void ap_mpm_rewrite_args(process_rec *process) +{ + apr_array_header_t *mpm_new_argv; + apr_status_t rv; + apr_getopt_t *opt; + char optbuf[3]; + const char *optarg; + + mpm_new_argv = apr_array_make(process->pool, process->argc, + sizeof(const char **)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + apr_getopt_init(&opt, process->pool, process->argc, process->argv); + opt->errfn = NULL; + optbuf[0] = '-'; + /* option char returned by apr_getopt() will be stored in optbuf[1] */ + optbuf[2] = '\0'; + while ((rv = apr_getopt(opt, "k:" AP_SERVER_BASEARGS, + optbuf + 1, &optarg)) == APR_SUCCESS) { + switch(optbuf[1]) { + case 'k': + if (!dash_k_arg) { + if (!strcmp(optarg, "start") || !strcmp(optarg, "stop") || + !strcmp(optarg, "restart") || !strcmp(optarg, "graceful") || + !strcmp(optarg, "graceful-stop")) { + dash_k_arg = optarg; + break; + } + } + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + if (optarg) { + *(const char **)apr_array_push(mpm_new_argv) = optarg; + } + } + } + + /* back up to capture the bad argument */ + if (rv == APR_BADCH || rv == APR_BADARG) { + opt->ind--; + } + + while (opt->ind < opt->argc) { + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, opt->argv[opt->ind++]); + } + + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *)mpm_new_argv->elts; + + if (NULL == dash_k_arg) { + dash_k_arg = dash_k_arg_noarg; + } + + APR_REGISTER_OPTIONAL_FN(ap_signal_server); +} + +static pid_t parent_pid, my_pid; +static apr_pool_t *pconf; + +#if AP_ENABLE_EXCEPTION_HOOK + +static int exception_hook_enabled; + +const char *ap_mpm_set_exception_hook(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + if (cmd->server->is_virtual) { + return "EnableExceptionHook directive not allowed in <VirtualHost>"; + } + + if (strcasecmp(arg, "on") == 0) { + exception_hook_enabled = 1; + } + else if (strcasecmp(arg, "off") == 0) { + exception_hook_enabled = 0; + } + else { + return "parameter must be 'on' or 'off'"; + } + + return NULL; +} + +static void run_fatal_exception_hook(int sig) +{ + ap_exception_info_t ei = {0}; + + if (exception_hook_enabled && + geteuid() != 0 && + my_pid != parent_pid) { + ei.sig = sig; + ei.pid = my_pid; + ap_run_fatal_exception(&ei); + } +} +#endif /* AP_ENABLE_EXCEPTION_HOOK */ + +/* handle all varieties of core dumping signals */ +static void sig_coredump(int sig) +{ + apr_filepath_set(ap_coredump_dir, pconf); + apr_signal(sig, SIG_DFL); +#if AP_ENABLE_EXCEPTION_HOOK + run_fatal_exception_hook(sig); +#endif + /* linuxthreads issue calling getpid() here: + * This comparison won't match if the crashing thread is + * some module's thread that runs in the parent process. + * The fallout, which is limited to linuxthreads: + * The special log message won't be written when such a + * thread in the parent causes the parent to crash. + */ + if (getpid() == parent_pid) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, + 0, ap_server_conf, APLOGNO(00060) + "seg fault or similar nasty error detected " + "in the parent process"); + /* XXX we can probably add some rudimentary cleanup code here, + * like getting rid of the pid file. If any additional bad stuff + * happens, we are protected from recursive errors taking down the + * system since this function is no longer the signal handler GLA + */ + } + kill(getpid(), sig); + /* At this point we've got sig blocked, because we're still inside + * the signal handler. When we leave the signal handler it will + * be unblocked, and we'll take the signal... and coredump or whatever + * is appropriate for this particular Unix. In addition the parent + * will see the real signal we received -- whereas if we called + * abort() here, the parent would only see SIGABRT. + */ +} + +AP_DECLARE(apr_status_t) ap_fatal_signal_child_setup(server_rec *s) +{ + my_pid = getpid(); + return APR_SUCCESS; +} + +/* We can't call sig_coredump (ap_log_error) once pconf is destroyed, so + * avoid double faults by restoring each default signal handler on cleanup. + */ +static apr_status_t fatal_signal_cleanup(void *unused) +{ + (void)unused; + + apr_signal(SIGSEGV, SIG_DFL); +#ifdef SIGBUS + apr_signal(SIGBUS, SIG_DFL); +#endif /* SIGBUS */ +#ifdef SIGABORT + apr_signal(SIGABORT, SIG_DFL); +#endif /* SIGABORT */ +#ifdef SIGABRT + apr_signal(SIGABRT, SIG_DFL); +#endif /* SIGABRT */ +#ifdef SIGILL + apr_signal(SIGILL, SIG_DFL); +#endif /* SIGILL */ +#ifdef SIGFPE + apr_signal(SIGFPE, SIG_DFL); +#endif /* SIGFPE */ + + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_fatal_signal_setup(server_rec *s, + apr_pool_t *in_pconf) +{ +#ifndef NO_USE_SIGACTION + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sigemptyset(&sa.sa_mask); + +#if defined(SA_ONESHOT) + sa.sa_flags = SA_ONESHOT; +#elif defined(SA_RESETHAND) + sa.sa_flags = SA_RESETHAND; +#endif + + sa.sa_handler = sig_coredump; + if (sigaction(SIGSEGV, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00061) "sigaction(SIGSEGV)"); +#ifdef SIGBUS + if (sigaction(SIGBUS, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00062) "sigaction(SIGBUS)"); +#endif +#ifdef SIGABORT + if (sigaction(SIGABORT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00063) "sigaction(SIGABORT)"); +#endif +#ifdef SIGABRT + if (sigaction(SIGABRT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00064) "sigaction(SIGABRT)"); +#endif +#ifdef SIGILL + if (sigaction(SIGILL, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00065) "sigaction(SIGILL)"); +#endif +#ifdef SIGFPE + if (sigaction(SIGFPE, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00066) "sigaction(SIGFPE)"); +#endif + +#else /* NO_USE_SIGACTION */ + + apr_signal(SIGSEGV, sig_coredump); +#ifdef SIGBUS + apr_signal(SIGBUS, sig_coredump); +#endif /* SIGBUS */ +#ifdef SIGABORT + apr_signal(SIGABORT, sig_coredump); +#endif /* SIGABORT */ +#ifdef SIGABRT + apr_signal(SIGABRT, sig_coredump); +#endif /* SIGABRT */ +#ifdef SIGILL + apr_signal(SIGILL, sig_coredump); +#endif /* SIGILL */ +#ifdef SIGFPE + apr_signal(SIGFPE, sig_coredump); +#endif /* SIGFPE */ + +#endif /* NO_USE_SIGACTION */ + + pconf = in_pconf; + parent_pid = my_pid = getpid(); + apr_pool_cleanup_register(pconf, NULL, fatal_signal_cleanup, + fatal_signal_cleanup); + + return APR_SUCCESS; +} + +#endif /* WIN32 */ |