/* * Copyright (c) 2000 Silicon Graphics, Inc. All rights reserved. */ /* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2000 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . * * Portions of this software are based upon public domain software * originally written at the National Center for Supercomputing Applications, * University of Illinois, Urbana-Champaign. */ /* * STM - State-Threaded Multi-Processing Module for Apache/2.0 * * Mike Abbott * Accelerating Apache Project - http://aap.sourceforge.net/ * State Threads Project - http://state-threads.sourceforge.net/ * * State threads are non-preemptive non-concurrent threads that run on * virtual processors which do not share data. Performance exceeds that * of concurrent threads such as pthreads because there is no mutual * exclusion locking and no proliferation of kernel execution vehicles * from use of blocking I/O system calls. State threads are extremely * lightweight, scheduling entirely in user mode. Hardware concurrency * is available by forking multiple virtual processors. * * The state-threaded multi-processing module (STM MPM) for Apache/2.0 * creates a constant number of virtual processors (VPs) and replaces * them when they die. Each virtual processor manages its own * independent set of state threads (STs), the number of which varies * with load against the server. Each state thread listens to, and * processes connections from, exactly one listening socket. * * When the main Apache server calls STM's ap_mpm_run() the initial * process becomes the watchdog, waiting for children (VPs) to die or * for a signal requesting shutdown or restart. (See stm_main().) This * is the master process. The master process mostly sits blocked in * wait(). It does not wake up periodically to perform maintenance such * as the scoreboard updates other MPMs do every second. Passive idling * via wait() is friendly to both low-volume and high-volume servers. * Low-volume servers benefit from passive idling because they can * completely page out Apache when it is idle for long periods and won't * have some CPU cache lines always containing Apache data. High-volume * servers benefit because there are no extraneous scheduling events and * the CPU caches remain hot with data useful to serving pages quickly. * * The master process forks off children which become virtual * processors. Each VP starts a number of state threads and then * becomes the primordial state thread which blocks waiting for input * from a pipe. (See stm_vp_main().) Only the VP itself writes to this * pipe, and only to begin graceful shutdown of the VP and all its * threads. This happens when a VP receives SIGHUP from the master * process or a VP has processed all of the connections it is allowed * and terminates itself (to reclaim resources leaked by faulty * modules). This signal is the only way the master process and the VPs * communicate (except for SIGTERM, upon receipt of which the VPs exit * immediately because they set its disposition to SIG_DFL). A pipe is * used because using write() on a pipe is guaranteed to be safe inside * a signal handler and no ST function -- such as st_cond_signal() -- is * safe to use inside a signal handler. * * Each state thread loops processing connections from a single * listening socket. (See stm_thread_main().) (Threads could span * multiple sockets but doing so would cloud the code a bit. There * would be no extra system calls since everything boils down to * select() in the state thread library anyway.) Only one ST runs on a * VP at a time, and VPs do not share memory (except for the * scoreboards, discussed below), so no mutual exclusion locking is * necessary on any data, and the entire server is free to use all the * static variables and non-reentrant library functions it wants, * greatly simplifying programming and debugging and increasing * performance. The current thread on each VP maintains equilibrium on * that VP, starting a new thread or terminating itself if the number of * spare threads exceeds the lower or upper limit, and also updates * counters and the scoreboard for that VP. * * Each VP has its own scoreboard. The scoreboards are memory-mapped * disk files (as opposed to anonymous shared memory) so that tools can * read scoreboard data without causing Apache to serve a page. Of * course, most of the scoreboard data is also available on the * server-status page from mod_status. VPs have read/write access to * all VPs' scoreboards but in practice read/write their own and only * read others'. * * All I/O operations on sockets must use the state thread library's I/O * functions because only those functions prevent blocking of the entire * VP process and perform state thread scheduling. No APR I/O function * must ever be used. The STM relies on the state thread I/O layer (ST * IOL) to direct I/O operations to ST I/O functions. * * Virtual processors may be bound to particular CPUs to improve cache * utilization, and may listen to all or a subset of the listen sockets. * Cache utilization is further improved when network interface * interrupts are bound to the same CPUs as the VPs listening to those * interfaces. Unfortunately intercepting Listen directives to support * this optimization introcudes some really ugly code. * * See htdocs/manual/stm.html for further documentation. */ #include #define CORE_PRIVATE #include "ap_config.h" #include "apr_portable.h" #include "apr_strings.h" #include "httpd.h" #include "http_main.h" #include "http_log.h" #include "http_config.h" #include "http_connection.h" #include "ap_listen.h" #include "ap_mpm.h" #include "mpm_status.h" #include "unixd.h" #include "stiol.h" #include #include #include #include #if defined(IRIX) && _MIPS_SIM != _ABIO32 /* disable warnings about unused formal parameters */ #pragma set woff 1174 #endif /* STM version identifier included in Server response header. */ static const char stm_version[] = "STM/1.0"; /* * Maximum number of listeners (Listen/VPListen directives, aka * accept()ing sockets). The default value is arbitrary but should be * kept small. Feel free to override the default with any value though. */ #ifndef STM_LISTENER_LIMIT #define STM_LISTENER_LIMIT 8 #endif /* * Maximum number of virtual processors. The default value is arbitrary * but should be kept small. Feel free to override the default with any * value though. */ #ifndef STM_VP_LIMIT #define STM_VP_LIMIT 8 #endif /* * Maximum number of state threads per virtual processor (upper bound on * MaxThreads). The default value is arbitrary but should be kept * moderate: not so small as to hinder performance, not so big as to * waste memory. Feel free to override the default with any value * though. The server-status page (see mod_status) reports the maximum * number of threads actually used at any time, which can be used to * tune this value optimally for your system and workload. */ #ifndef STM_ST_LIMIT #define STM_ST_LIMIT 512 #endif /* * Maximum length in bytes of the key string in the scoreboards' * connection status tables, including the string-terminating null. * Keys longer than this will be truncated. The default value is * arbitrary but should be kept small. Feel free to override the * default with any value though. It's probably a good idea to keep * STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE one or more times the size * of your system's largest cache line. */ #ifndef STM_SCORE_KEY_SIZE #define STM_SCORE_KEY_SIZE 16 #endif /* * Maximum length in bytes of the value string in the scoreboards' * connection status tables, including the string-terminating null. * Values longer than this will be truncated. The default value is * arbitrary but should be kept moderate. Feel free to override the * default with any value though. It's probably a good idea to keep * STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE one or more times the size * of your system's largest cache line. */ #ifndef STM_SCORE_VALUE_SIZE #define STM_SCORE_VALUE_SIZE 48 #endif /* * Maximum number of key/value pairs in the scoreboards' connection * status tables. When the table fills additional insertions are * ignored. The default value is arbitrary but should be kept small. * Feel free to override the default with any value though. */ #ifndef STM_SCORE_LIMIT #define STM_SCORE_LIMIT 16 #endif /* Debugging aids. */ #ifdef STM_DEBUG # define STM_ASSERT(x) { \ if (!(x)) { \ fprintf(stderr, "STM assertion botched: \"%s\", %s line %d, pid %d, thread %p\n", \ #x, __FILE__, __LINE__, getpid(), stm_thread_self()); \ abort(); \ } \ } # define STM_TRACE(x) if (stm.debug.trace) printf x # define HIDDEN /* nothing */ #else # define STM_ASSERT(x) /* nothing */ # define STM_TRACE(x) /* nothing */ # define HIDDEN static #endif /* Configured VPListen directives. */ typedef struct stm_config_vp_listen { int num_listeners; /* number of listeners */ struct stm_listener *listeners[STM_LISTENER_LIMIT]; /* ptrs to stm.listeners */ } stm_config_vp_listen; /* * Configuration, defaults, and methods. All STM configuration settings * are kept in this structure. */ typedef struct stm_config { /* * Number of virtual processors. Must be between 1 and STM_VP_LIMIT * inclusive. Only one or a few are needed per CPU. The server * starts a replacement when a VP dies so the number running is * constant. Making this too small can cause long latency for * clients; too big and you waste CPU cycles and memory. */ int num_vps; #define STM_CMD_NUM_VPS "NumVPs" /* * Number of state threads each VP should start initially PER SOCKET * -- not per VP. Must be between 1 and STM_ST_LIMIT inclusive. * Should be between 1 and max_threads/num_listeners inclusive. * Generally this value should be the same as min_spare_threads but * it can be higher to facilitate Apache restarts on high-volume * servers. */ int start_threads; #define STM_CMD_START_THREADS "StartThreads" /* * Minimum number of spare state threads PER SOCKET -- not per VP. * Spare threads are threads awaiting a connection (as opposed to * processing requests on established connections). Must be between * 1 and STM_ST_LIMIT inclusive. Should be between 1 and * max_threads/num_listeners inclusive. Making this too small can * cause long latency for clients; too big and you waste CPU cycles * on low-volume servers. */ int min_spare_threads; #define STM_CMD_MIN_SPARE_THREADS "MinSpareThreads" /* * Maximum number of spare state threads PER SOCKET -- not per VP. * See min_spare_threads. */ int max_spare_threads; #define STM_CMD_MAX_SPARE_THREADS "MaxSpareThreads" /* * Maximum number of state threads -- spare or busy -- per VP across * all sockets. Must be between 1 and STM_ST_LIMIT inclusive. * Making this too small can cause long latency for clients, * especially if your site is largely dynamic (e.g. CGI); too big * and you waste memory. */ int max_threads; #define STM_CMD_MAX_THREADS "MaxThreads" /* * Size in bytes of each thread's stack. Must be between the * system's page size and INT_MAX inclusive. Making this too small * can cause stack overflow errors; too big and you waste memory. */ int stack_size; #define STM_CMD_STACK_SIZE "StackSize" /* * Number of connections (not requests) each thread should serve * before yielding the VP to another thread. Must be between 0 and * INT_MAX inclusive. Making this too small can waste CPU cycles * (by forcing a select() too often); too big (or 0) can cause long * latency for clients and also inaccurate time stamps in log files * (with USE_ST_TIME). The value 0 means the number of connections * is unlimited. */ int thread_connections; #define STM_CMD_THREAD_CONNECTIONS "ThreadConnections" /* * Number of connections (not requests) each VP should serve before * exiting gracefully and allowing the master process to replace it * to reclaim resources such as memory or file descriptors leaked by * faulty modules. Must be between 0 and INT_MAX inclusive. Making * this too small can waste CPU cycles; too big (or 0) and leaked * resources can interfere with normal system operation. The value * 0 means the number of connections is unlimited and VPs should * never voluntarily exit. */ int vp_connections; #define STM_CMD_VP_CONNECTIONS "VPConnections" /* * Binding VPs to CPUs. If vp_bind is non-null, vp_bind[n] is the * CPU number to which VP n should be bound. */ int *vp_bind; #define STM_CMD_VP_BIND "VPBind" /* * Binding listeners to VPs. If vp_listen is non-null, vp_listen[n] * is a vector of IP addresses to which VP n should listen. */ stm_config_vp_listen *vp_listen; #define STM_CMD_VP_LISTEN "VPListen" /* * Path of the file where Apache writes the PID of the master * process. The file may or may not exist before startup. Except * for the defaults structure this is always an absolute path name. */ const char *pid_file; #define STM_CMD_PID_FILE "PidFile" /* * Flag enabling or disabling connection status in scoreboards (they * can be slow to maintain). Zero means disabled, nonzero means * enabled. */ int connection_status; #define STM_CMD_CONNECTION_STATUS "ConnectionStatus" /* * Path of the directory where the STM creates scoreboard files for * each VP. The directory must already exist before Apache startup. * The scoreboard files themselves may or may not pre-exist, have * names that are just the ordinal of the VP, and have a data format * described in the scoreboard section below. Except for the * defaults structure this is always an absolute path name. */ const char *scoreboard_dir; #define STM_CMD_SCOREBOARD_DIR "ScoreboardDir" /* * Path of a directory where Apache will dump core if necessary. * The directory should exist before Apache startup. Except for the * defaults structure this is always an absolute path name. A NULL * value means Apache should dump core in whatever directory it * happens to be in at the time, generally the server root directory. */ const char *core_dir; #define STM_CMD_CORE_DIR "CoreDir" } stm_config; HIDDEN const stm_config stm_config_defaults = { 4, /* num_vps */ 5, /* start_threads */ 5, /* min_spare_threads */ 10, /* max_spare_threads */ 64, /* max_threads */ 64 * 1024, /* stack_size */ 0, /* thread_connections */ 0, /* vp_connections */ NULL, /* vp_bind */ NULL, /* vp_listen */ "logs/httpd.pid", /* pid_file */ 1, /* connection_status */ "scoreboards", /* scoreboard_dir */ NULL /* core_dir */ }; HIDDEN const char *stm_config_set_num(cmd_parms *, const char *, int *, int, int, const char *); HIDDEN const char *stm_config_set_num_vps(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_start_threads(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_min_spare_threads(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_max_spare_threads(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_max_threads(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_stack_size(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_thread_connections(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_vp_connections(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_vp_bind(cmd_parms *, void *, const char *, const char *); HIDDEN const char *stm_config_set_vp_listen(cmd_parms *, void *, const char *, const char *); HIDDEN const char *stm_config_set_ap_listen(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_pid_file(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_connection_status(cmd_parms *, void *, int); HIDDEN const char *stm_config_set_scoreboard_dir(cmd_parms *, void *, const char *); HIDDEN const char *stm_config_set_core_dir(cmd_parms *, void *, const char *); HIDDEN const command_rec stm_cmds[] = { AP_INIT_TAKE1( STM_CMD_NUM_VPS, stm_config_set_num_vps, NULL, RSRC_CONF, "Number of virtual processors" ), AP_INIT_TAKE1( STM_CMD_START_THREADS, stm_config_set_start_threads, NULL, RSRC_CONF, "Initial number of state threads per listen socket" ), AP_INIT_TAKE1( STM_CMD_MIN_SPARE_THREADS, stm_config_set_min_spare_threads, NULL, RSRC_CONF, "Minumum number of spare state threads per listen socket" ), AP_INIT_TAKE1( STM_CMD_MAX_SPARE_THREADS, stm_config_set_max_spare_threads, NULL, RSRC_CONF, "Maxumum number of spare state threads per listen socket" ), AP_INIT_TAKE1( STM_CMD_MAX_THREADS, stm_config_set_max_threads, NULL, RSRC_CONF, "Maximum number of state threads per virtual processor" ), AP_INIT_TAKE1( STM_CMD_STACK_SIZE, stm_config_set_stack_size, NULL, RSRC_CONF, "Size in bytes of each thread's stack" ), AP_INIT_TAKE1( STM_CMD_THREAD_CONNECTIONS, stm_config_set_thread_connections, NULL, RSRC_CONF, "Number of connections each thread should serve before yielding" ), AP_INIT_TAKE1( STM_CMD_VP_CONNECTIONS, stm_config_set_vp_connections, NULL, RSRC_CONF, "Number of connections each virtual processor should serve before exiting" ), AP_INIT_TAKE2( STM_CMD_VP_BIND, stm_config_set_vp_bind, NULL, RSRC_CONF, "Bind a virtual processor to a specific CPU" ), AP_INIT_ITERATE2( STM_CMD_VP_LISTEN, stm_config_set_vp_listen, NULL, RSRC_CONF, "List of ip-address:port-numbers to which a virtual processor should listen" ), AP_INIT_TAKE1( STM_CMD_PID_FILE, stm_config_set_pid_file, NULL, RSRC_CONF, "Name of file in which the server records its PID upon startup" ), AP_INIT_FLAG( STM_CMD_CONNECTION_STATUS, stm_config_set_connection_status, NULL, RSRC_CONF, "Enables/disables connection status information in the scoreboards" ), AP_INIT_TAKE1( STM_CMD_SCOREBOARD_DIR, stm_config_set_scoreboard_dir, NULL, RSRC_CONF, "Name of existing directory in which the server maintains scoreboard file(s)" ), AP_INIT_TAKE1( STM_CMD_CORE_DIR, stm_config_set_core_dir, NULL, RSRC_CONF, "Name of existing directory in which the server should dump core if necessary" ), UNIX_DAEMON_COMMANDS /* * Redirect Listen directives to our own handler. This is a gross * kludge but so is the existence of, definition of, and MPMs' * dependence on the LISTEN_COMMANDS macro. */ #define ap_set_listener stm_config_set_ap_listen LISTEN_COMMANDS #undef ap_set_listener { NULL } }; HIDDEN void stm_pre_config(apr_pool_t *, apr_pool_t *, apr_pool_t *); HIDDEN void stm_post_config(apr_pool_t *, apr_pool_t *, apr_pool_t *, server_rec *); HIDDEN void stm_hooks(void); /* VPs and STs health indicators. */ typedef enum stm_health { STM_HEALTH_DEAD, /* entity is not running */ STM_HEALTH_DYING, /* entity has been asked to terminate */ STM_HEALTH_ALIVE /* entity is running */ } stm_health; /* Master process state. */ typedef enum stm_state { STM_STATE_SHUTDOWN, /* shutdown requested or in progress */ STM_STATE_RUN, /* running */ STM_STATE_GRACEFUL, /* graceful restart requested or in progress */ STM_STATE_RESTART /* restart requested or in progress */ } stm_state; /* Evaluation of seriousness of child process's death. */ typedef enum stm_grief { STM_GRIEF_NORMAL, /* normal and/or expected */ STM_GRIEF_FATAL, /* catastrophic */ STM_GRIEF_SURPRISE /* unexpected but not tragic */ } stm_grief; #ifdef STM_DEBUG /* Debugging aids. */ typedef struct stm_debug { int interactive; /* when nonzero don't detach */ int one_process; /* when nonzero restrict to a single process */ int trace; /* when nonzero trace function calls */ } stm_debug; #endif /* Datum about a connection. Key/value type mandated by mod_status. */ typedef struct stm_score { char key[STM_SCORE_KEY_SIZE]; char value[STM_SCORE_VALUE_SIZE]; } stm_score; /* * Per-VP scoreboard and methods. Fields are explicitly sized because * this is also the on-disk file format. When changing this structure * also change the version string so tools can correctly parse the data * and also update the stmstat tool. The version string should be * exactly STM_SCOREBOARD_VERSION_LENGTH bytes long excluding the null. * Do not change STM_SCOREBOARD_VERSION_LENGTH. */ #define STM_SCOREBOARD_VERSION_LENGTH 16 #define STM_SCOREBOARD_VERSION "STM-MPM-sb-0002\n" typedef struct stm_scoreboard { char version[STM_SCOREBOARD_VERSION_LENGTH]; /* see above */ apr_int32_t st_limit; /* size of arrays below */ apr_int32_t listener_limit; /* size of arrays below */ apr_int32_t score_limit; /* size of arrays below */ apr_int32_t key_size; /* size of arrays below */ apr_int32_t value_size; /* size of arrays below */ apr_int32_t pid; /* this VP's PID */ apr_time_t start_time; /* server's start time */ apr_time_t vp_start_time; /* this VP's start time */ apr_int32_t incarnations; /* # of different VPs in this slot */ apr_int32_t cpu; /* CPU to which this VP is bound */ apr_int32_t thread_starts; /* # of thread start events */ apr_int32_t thread_exits; /* # of thread exit events */ apr_int32_t most_threads; /* most threads ever used */ apr_int32_t historic_vp_connections;/* # of conns, prior incarnations */ apr_int32_t vp_connections; /* # of conns, this incarnation */ apr_int32_t thread_connections[STM_ST_LIMIT]; /* per thread */ apr_int32_t listener_connections[STM_LISTENER_LIMIT]; /* per listener */ char listeners[STM_LISTENER_LIMIT][24]; /* IP address:port */ stm_score scores[STM_ST_LIMIT][STM_SCORE_LIMIT]; /* per-conn data */ } stm_scoreboard; HIDDEN stm_scoreboard *stm_scoreboard_new(int); HIDDEN void stm_scoreboard_init(stm_scoreboard *); /* * Listener and methods. These listeners are wrappers around the more * traditional ap_listen_rec so we can accept on an st_netfd_t rather * than an apr_socket_t. */ typedef apr_uint32_t stm_port; /* simplify profusion of IP port types */ typedef apr_uint32_t stm_addr; /* simplify profusion of IP address types */ typedef struct stm_listener { int id; /* == (int) (this - stm.listeners) */ const char *addr_string; /* inet_ntoa(addr.sin_addr) */ struct sockaddr_in addr; /* IP addr and port in network order */ int unique; /* is addr unique (not INADDR_ANY)? */ int share_count; /* number of VPs using this listener */ ap_listen_rec *alr; /* standard listener */ st_netfd_t sd; /* ST version of alr->sd */ int spare_threads; /* # of spare threads right now */ } stm_listener; HIDDEN void stm_listener_init(stm_listener *); HIDDEN stm_listener *stm_listener_add(const char *, stm_addr, stm_port, int *); HIDDEN int stm_listener_usurp(stm_listener *, ap_listen_rec *); HIDDEN void stm_listener_revert(stm_listener *); /* * Thread and methods. */ typedef struct stm_thread { int id; /* == (int) (this - vp->threads) */ stm_health health; /* how is this thread doing? */ st_thread_t st; /* ST for this thread */ struct stm_vp *vp; /* VP on which this thread runs */ stm_listener *listener; /* listener on which to accept() */ int connected; /* connected to a client right now? */ int connections; /* conns remaining before yield */ } stm_thread; HIDDEN void stm_thread_init(stm_thread *, struct stm_vp *); HIDDEN int stm_thread_start(stm_thread *, stm_listener *); HIDDEN stm_thread *stm_thread_self(void); HIDDEN void *stm_thread_main(void *); HIDDEN void stm_thread_process_connection(const stm_listener *, apr_pool_t *, st_netfd_t, const struct sockaddr_in *, long); HIDDEN void stm_thread_stop(stm_thread *); HIDDEN int stm_thread_stopped(stm_thread *); /* * Virtual processor and methods. */ typedef struct stm_vp { int id; /* == (int) (this - stm.vps) */ pid_t pid; /* PID of VP process */ stm_health health; /* how is this VP doing? */ apr_pool_t *pool; /* pool for lifetime of VP */ int stop_pipe[2]; /* for asynchronous-safe termination */ int num_listeners; /* number of listeners for this VP */ stm_listener *listeners[STM_LISTENER_LIMIT]; /* ptrs to stm.listeners */ int connections; /* conns remaining before suicide */ int max_threads_hit; /* reported hitting thread ceiling? */ int num_threads; /* number of non-dead threads */ stm_thread threads[STM_ST_LIMIT]; /* threads */ stm_thread *dead_threads[STM_ST_LIMIT];/* to recycle them quickly */ stm_scoreboard *scoreboard; /* scoreboard in shared memory */ } stm_vp; HIDDEN int stm_vp_init(stm_vp *); HIDDEN int stm_vp_listen(stm_vp *); HIDDEN int stm_vp_start(stm_vp *); HIDDEN void stm_vp_bind(stm_vp *); HIDDEN void stm_vp_signals(void); HIDDEN void stm_vp_main(stm_vp *); HIDDEN void stm_vp_stop(stm_vp *); HIDDEN void stm_vp_stop_self(stm_vp *); HIDDEN void stm_vp_onsig_stop(int); HIDDEN void stm_vp_onexit(void); HIDDEN apr_status_t stm_vp_cleanup(void *); /* * Global data and methods. All STM data is in this one place for easy * debugging. */ HIDDEN struct stm { apr_time_t start_time; /* server start time */ apr_pool_t *pool; /* main pool */ server_rec *server; /* for logging */ stm_state state; /* run state */ int initialized; /* stm_init() ever called? */ int num_restarts; /* number of server restarts */ pid_t pid; /* pid of master process */ stm_config config; /* config parameters */ int num_listeners; /* number of listeners */ stm_listener listeners[STM_LISTENER_LIMIT]; int num_vps; /* number of VPs */ stm_vp vps[STM_VP_LIMIT]; /* table of VP info */ int self_key; /* for stm_thread_self() */ #ifdef STM_DEBUG stm_debug debug; /* debugging parameters */ #endif } stm; HIDDEN int stm_init(apr_pool_t *, server_rec *); HIDDEN void stm_fini(void); HIDDEN void stm_signals(void); HIDDEN void stm_main(void); HIDDEN void stm_reap(pid_t, int); HIDDEN stm_grief stm_mourn(pid_t, int); HIDDEN void stm_doomsday(void); /* Signal helper routines */ HIDDEN void stm_signal_catch(int, void (*)(int)); HIDDEN void stm_signal_default(int); HIDDEN void stm_signal_ignore(int); HIDDEN void stm_signal_core(int); /* Master process's signal handlers */ HIDDEN void stm_onsig_core(int); HIDDEN void stm_onsig_restart(int); HIDDEN void stm_onsig_shutdown(int); /* Miscellany */ HIDDEN long stm_id_make(int, int); HIDDEN int stm_id_break(long, int *, int *); HIDDEN void stm_disable_nagle(st_netfd_t); /* Exported API routines */ void ap_start_shutdown(void); void ap_start_restart(int); int ap_graceful_stop_signalled(void); void ap_reset_connection_status(long); void ap_update_connection_status(long, const char *, const char *); apr_array_header_t *ap_get_status_table(apr_pool_t *); int ap_mpm_run(apr_pool_t *, apr_pool_t *, server_rec *); /* Module definition */ module MODULE_VAR_EXPORT mpm_stm_module = { MPM20_MODULE_STUFF, NULL, /* run before apache parses argv */ NULL, /* create per-directory config structure */ NULL, /* merge per-directory config structures */ NULL, /* create per-server config structure */ NULL, /* merge per-server config structures */ stm_cmds, /* command table */ NULL, /* handlers */ stm_hooks /* register_hooks */ }; /*********************************************************************** * Configuration */ /* * Standard MPM pre-config hook: initializes/resets all configuration * parameters to their default values. */ HIDDEN void stm_pre_config(apr_pool_t *conf_pool, apr_pool_t *log_pool, apr_pool_t *tmp_pool) { int i; #ifdef STM_DEBUG if (stm.pid == 0) { /* set these just once */ stm.debug.interactive = getenv("STM_INTERACTIVE") != NULL; stm.debug.one_process = getenv("ONE_PROCESS") != NULL; stm.debug.trace = getenv("STM_TRACE") != NULL; } #endif STM_TRACE(("%d: stm_pre_config() #%d\n", getpid(), stm.num_restarts)); /* it would be bad to reset stm.config accidentally while running */ STM_ASSERT(stm.state != STM_STATE_RUN); /* called twice on normal startup; detach only on second one */ if (stm.num_restarts++ == 1) { #ifdef STM_DEBUG setlinebuf(stdout); if (!stm.debug.interactive && !stm.debug.one_process) #endif if (apr_detach() != APR_SUCCESS) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, errno, NULL, "stm_pre_config: could not detach from controlling terminal: apr_detach"); stm.pid = getpid(); } unixd_pre_config(); ap_listen_pre_config(); stm.num_listeners = 0; for (i = 0; i < STM_LISTENER_LIMIT; i++) stm_listener_init(&stm.listeners[i]); if (stm.config.vp_bind) { free(stm.config.vp_bind); stm.config.vp_bind = NULL; } if (stm.config.vp_listen) { free(stm.config.vp_listen); stm.config.vp_listen = NULL; } stm.config = stm_config_defaults; stm.config.pid_file = ap_server_root_relative(conf_pool, stm_config_defaults.pid_file); stm.config.scoreboard_dir = ap_server_root_relative(conf_pool, stm_config_defaults.scoreboard_dir); stm.config.core_dir = apr_pstrdup(conf_pool, ap_server_root); } /* * Standard MPM post-config hook. */ HIDDEN void stm_post_config(apr_pool_t *conf_pool, apr_pool_t *log_pool, apr_pool_t *tmp_pool, server_rec *server) { STM_TRACE(("%d: stm_post_config()\n", getpid())); #ifndef NO_STM_VERSION ap_add_version_component(conf_pool, stm_version); #endif } /* * Standard module hook registration. */ HIDDEN void stm_hooks(void) { STM_TRACE(("%d: stm_hooks()\n", getpid())); INIT_SIGLIST() ap_hook_pre_config(stm_pre_config, NULL, NULL, AP_HOOK_MIDDLE); ap_hook_post_config(stm_post_config, NULL, NULL, AP_HOOK_MIDDLE); } /* * Utility function to set an integer configuration parameter if the new * value is in bounds: * max_value >= 0 min_value <= atoi(value_string) <= max_value * max_value < 0 min_value <= atoi(value_string) * Returns an error string, or NULL on success. */ HIDDEN const char * stm_config_set_num(cmd_parms *cmd, const char *value_string, int *paramp, int min_value, int max_value, const char *max_name) { const char *err; int value; STM_TRACE(("%d: stm_config_set_num(cmd->\"%s\", value_string=\"%s\", min_value=%d, max_value=%d, max_name=\"%s\")\n", getpid(), cmd->cmd->name, value_string, min_value, max_value, max_name)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL) { value = atoi(value_string); if (value >= min_value) { if (max_value < 0 || value <= max_value) *paramp = value; else { ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, "value %d for %s is above maximum %d, using maximum" "; re-compile with larger %s if desired", value, cmd->cmd->name, max_value, max_name); *paramp = max_value; } } else { ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, "value %d for %s is below minimum %d, using minimum", value, cmd->cmd->name, min_value); *paramp = min_value; } } return err; } /* * Set the num_vps configuration parameter if the new value is in * bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_num_vps(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.num_vps, 1, STM_VP_LIMIT, "STM_VP_LIMIT"); } /* * Set the start_threads configuration parameter if the new value is in * bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_start_threads(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.start_threads, 1, STM_ST_LIMIT, "STM_ST_LIMIT"); } /* * Set the min_spare_threads configuration parameter if the new value is * in bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_min_spare_threads(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.min_spare_threads, 1, STM_ST_LIMIT, "STM_ST_LIMIT"); } /* * Set the max_spare_threads configuration parameter if the new value is * in bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_max_spare_threads(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.max_spare_threads, 1, STM_ST_LIMIT, "STM_ST_LIMIT"); } /* * Set the max_threads configuration parameter if the new value is in * bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_max_threads(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.max_threads, 1, STM_ST_LIMIT, "STM_ST_LIMIT"); } /* * Set the stack_size configuration parameter if the new value is in * bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_stack_size(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.stack_size, getpagesize(), -1, NULL); } /* * Set the thread_connections configuration parameter if the new value * is in bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_thread_connections(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.thread_connections, 0, -1, NULL); } /* * Set the vp_connections configuration parameter if the new value is in * bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_vp_connections(cmd_parms *cmd, void *dummy, const char *num) { return stm_config_set_num(cmd, num, &stm.config.vp_connections, 0, -1, NULL); } /* * Add to the vp_bind configuration parameter if the new values are in * bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_vp_bind(cmd_parms *cmd, void *dummy, const char *vp_string, const char *cpu_string) { const char *err; STM_TRACE(("%d: stm_config_set_vp_bind(vp_string=\"%s\", cpu_string=\"%s\")\n", getpid(), vp_string, cpu_string)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL) { int v; char *stop; v = (int) strtol(vp_string, &stop, 0); if (stop != vp_string && *stop == 0) { /* * Compare against STM_VP_LIMIT here because num_vps may not * have been set yet (could appear later in config file). * stm_init() warns about bindings for unused VPs. */ if (v >= 0 && v < STM_VP_LIMIT) { int c; c = (int) strtol(cpu_string, &stop, 0); if (stop != cpu_string && *stop == 0 && c >= 0) { if (stm.config.vp_bind == NULL) { int i; stm.config.vp_bind = (int *) malloc(STM_VP_LIMIT * sizeof *stm.config.vp_bind); for (i = 0; i < STM_VP_LIMIT; i++) stm.config.vp_bind[i] = -1; } stm.config.vp_bind[v] = c; } else err = "CPU identifier must be a non-negative number"; } else err = apr_psprintf(cmd->pool, "value %d for %s is out of range, must be between 0 and %d inclusive" "; re-compile with larger STM_VP_LIMIT if desired", v, cmd->cmd->name, STM_VP_LIMIT - 1); } else err = "virtual processor identifier must be a number"; } return err; } /* * Add to the vp_listen configuration parameter if the new values are in * bounds. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_vp_listen(cmd_parms *cmd, void *dummy, const char *vp_string, const char *listen_string) { const char *err; STM_TRACE(("%d: stm_config_set_vp_listen(vp_string=\"%s\", listen_string=\"%s\")\n", getpid(), vp_string, listen_string)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL && ap_listeners && stm.config.vp_listen == NULL) err = "cannot mix Listen and " STM_CMD_VP_LISTEN " directives."; if (err == NULL) { int v; char *stop; v = (int) strtol(vp_string, &stop, 0); if (stop != vp_string && *stop == 0) { /* * Compare against STM_VP_LIMIT here because num_vps may * not have been set yet (could appear later in config * file). stm_init() warns about bindings for unused * VPs. */ if (v >= 0 && v < STM_VP_LIMIT) { char *listen_copy; char *port_string; listen_copy = apr_pstrdup(cmd->pool, listen_string); port_string = strchr(listen_copy, ':'); if (port_string) { stm_port port; *port_string++ = 0; /* should be strtoul() but can't use it, oh well */ port = (stm_port) strtol(port_string, &stop, 0); if (stop != port_string && *stop == 0) { int addr_ok; struct in_addr addr; port = htons(port); addr_ok = inet_aton(listen_copy, &addr); if (!addr_ok && !strcmp(listen_copy, "*")) { addr.s_addr = htonl(INADDR_ANY); addr_ok = 1; } if (addr_ok) { stm_config_vp_listen *vlp; int new; stm_listener *lp; if (stm.config.vp_listen == NULL) stm.config.vp_listen = (stm_config_vp_listen *) calloc(STM_VP_LIMIT, sizeof *stm.config.vp_listen); vlp = &stm.config.vp_listen[v]; new = 0; lp = (vlp->num_listeners < STM_LISTENER_LIMIT) ? stm_listener_add(listen_copy, addr.s_addr, port, &new) : NULL; if (lp) { vlp->listeners[vlp->num_listeners++] = lp; if (new) { /* * Call the regular Listen directive * handler to add a standard * listener to ap_listeners. This * is a gross kludge but so is the * way Apache manages listeners. */ err = ap_set_listener(cmd, dummy, (addr.s_addr == htonl(INADDR_ANY)) ? port_string : listen_string); /* * An error from ap_set_listener() * means our call to it is wrong and * should be fixed. */ STM_ASSERT(err == NULL); } } else err = "too many listeners; re-compile with larger STM_LISTENER_LIMIT if desired"; } else err = "invalid IP address"; } else err = "invalid port number"; } else err = "listen address must be of the form: ip-address:port-number or *:port-number"; } else err = apr_psprintf(cmd->pool, "value %d for %s is out of range, must be between 0 and %d inclusive" "; re-compile with larger STM_VP_LIMIT if desired", v, cmd->cmd->name, STM_VP_LIMIT - 1); } else err = "virtual processor identifier must be a number"; } return err; } /* * Intercept standard Listen directives, saving information that is not * readily available later. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_ap_listen(cmd_parms *cmd, void *dummy, const char *listen_string) { const char *err; STM_TRACE(("%d: stm_config_set_ap_listen(listen_string=\"%s\")\n", getpid(), listen_string)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL && stm.config.vp_listen) err = "cannot mix Listen and " STM_CMD_VP_LISTEN " directives"; if (err == NULL) { char *ips, *ports; stm_port port; struct in_addr addr; /* * Parse the Listen directive's argument so we can fill in the * address and port number in the stm_listener. Then call the * regular Listen directive handler since we can't call * alloc_listener() directly. */ ips = apr_pstrdup(cmd->pool, listen_string); /* ***************************************** * begin copied code from ap_set_listener() */ ports = strchr(ips, ':'); if (ports != NULL) { if (ports == ips) { return "Missing IP address"; } else if (ports[1] == '\0') { return "Address must end in :"; } *(ports++) = '\0'; } else { ports = ips; } port = atoi(ports); if (!port) { return "Port must be numeric"; } if (ports == ips) { /* no address */ ips = APR_ANYADDR; } else { ips[(ports - ips) - 1] = '\0'; } /* * end copied code from ap_set_listener() ***************************************** */ if (inet_aton(ips, &addr)) { int new; if (stm_listener_add(ips, addr.s_addr, htons(port), &new)) { if (new) err = ap_set_listener(cmd, dummy, listen_string); /* else ignore it */ } else err = "too many listeners; re-compile with larger STM_LISTENER_LIMIT if desired"; } else err = "invalid IP address"; } return err; } /* * Set the pid_file configuration parameter. Returns error string, or * NULL on success. */ HIDDEN const char * stm_config_set_pid_file(cmd_parms *cmd, void *dummy, const char *path) { const char *err; STM_TRACE(("%d: stm_config_set_pid_file(path=\"%s\")\n", getpid(), path)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL) stm.config.pid_file = ap_server_root_relative(stm.pool, path); return err; } /* * Set the connection_status configuration parameter. Returns error * string, or NULL on success. */ HIDDEN const char * stm_config_set_connection_status(cmd_parms *cmd, void *dummy, int flag) { const char *err; STM_TRACE(("%d: stm_config_set_connection_status(flag=%d)\n", getpid(), flag)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL) stm.config.connection_status = flag; return err; } /* * Set the scoreboard_dir configuration parameter if the directory * exists. Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_scoreboard_dir(cmd_parms *cmd, void *dummy, const char *path) { const char *err; STM_TRACE(("%d: stm_config_set_scoreboard_dir(path=\"%s\")\n", getpid(), path)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL) { const char *rpath; struct stat stbuf; rpath = ap_server_root_relative(stm.pool, path); if (stat(rpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode)) stm.config.scoreboard_dir = rpath; else err = apr_psprintf(cmd->pool, "%s \"%s\" does not exist or is not a directory", cmd->cmd->name, rpath); } return err; } /* * Set the core_dir configuration parameter if the directory exists. * Returns error string, or NULL on success. */ HIDDEN const char * stm_config_set_core_dir(cmd_parms *cmd, void *dummy, const char *path) { const char *err; STM_TRACE(("%d: stm_config_set_core_dir(path=\"%s\")\n", getpid(), path)); err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err == NULL) { const char *rpath; struct stat stbuf; rpath = ap_server_root_relative(stm.pool, path); if (stat(rpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode)) stm.config.core_dir = rpath; else err = apr_psprintf(cmd->pool, "%s \"%s\" does not exist or is not a directory", cmd->cmd->name, rpath); } return err; } /*********************************************************************** * Scoreboard */ /* * Create and return a pointer to a new scoreboard in shared memory for * VP slot n, or NULL on failure. */ HIDDEN stm_scoreboard * stm_scoreboard_new(int n) { stm_scoreboard *sbp; char *path; int fd; STM_TRACE(("%d: stm_scoreboard_new(n=%d)\n", getpid(), n)); sbp = NULL; path = apr_psprintf(stm.pool, "%s/%d", stm.config.scoreboard_dir, n); fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd >= 0) { stm_scoreboard zsc; /* zero-fill the new file to exactly the right length */ memset(&zsc, 0, sizeof zsc); if (write(fd, &zsc, sizeof zsc) == sizeof zsc) { void *vp; vp = mmap(0, sizeof *sbp, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (vp != (void *) -1) sbp = (stm_scoreboard *) vp; else ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_scoreboard_new: cannot map %ld bytes read-write in \"%s\": mmap", (long) sizeof *sbp, path); } else ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_scoreboard_new: cannot zero-fill %ld bytes in \"%s\": write", (long) sizeof zsc, path); close(fd); } else ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_scoreboard_new: cannot create \"%s\" read-write: open", path); return sbp; } /* * Initialize/reset the given scoreboard. */ HIDDEN void stm_scoreboard_init(stm_scoreboard *sb) { int i, j; STM_TRACE(("%d: stm_scoreboard_init(sb=%p)\n", getpid(), sb)); /* * All the values start out as zero but this function is used for * both init and reset. Resetting a field to zero here makes it * keep track of only one incarnation of a VP. Not resetting it * makes it keep track since server start. */ memcpy(sb->version, STM_SCOREBOARD_VERSION, sizeof sb->version); sb->st_limit = STM_ST_LIMIT; sb->listener_limit = STM_LISTENER_LIMIT; sb->score_limit = STM_SCORE_LIMIT; sb->key_size = STM_SCORE_KEY_SIZE; sb->value_size = STM_SCORE_VALUE_SIZE; sb->pid = -1; sb->start_time = stm.start_time; sb->vp_start_time = 0; /* leave sb->incarnations alone */ sb->cpu = -1; sb->thread_starts = 0; sb->thread_exits = 0; /* leave sb->most_threads alone */ sb->historic_vp_connections += sb->vp_connections; sb->vp_connections = 0; for (i = 0; i < STM_ST_LIMIT; i++) { sb->thread_connections[i] = 0; for (j = 0; j < STM_SCORE_LIMIT; j++) sb->scores[i][j].key[0] = 0; } for (i = 0; i < STM_LISTENER_LIMIT; i++) { sb->listener_connections[i] = 0; sb->listeners[i][0] = 0; } } /*********************************************************************** * Listeners */ /* * Initialize/reset the given listener. */ HIDDEN void stm_listener_init(stm_listener *listener) { STM_TRACE(("%d: stm_listener_init(listener=%p,#%d)\n", getpid(), listener, (int) (listener - stm.listeners))); listener->id = (int) (listener - stm.listeners); STM_ASSERT(listener->id >= 0 && listener->id < STM_LISTENER_LIMIT); listener->addr_string = NULL; memset(&listener->addr, 0, sizeof listener->addr); listener->unique = 0; listener->share_count = 0; listener->alr = NULL; STM_ASSERT(listener->sd == NULL); /* init or stm_listener_revert() */ listener->sd = NULL; listener->spare_threads = 0; } /* * Add the given IP address and port number (both in network byte order) * to the list of listeners if not already there. Sets *new to 0 if * already there or to 1 if not. Returns the new listener, or NULL if * the listener table already contains the maximum number * (STM_LISTENER_LIMIT) of listeners. */ HIDDEN stm_listener * stm_listener_add(const char *addr_string, stm_addr addr, stm_port port, int *new) { stm_listener *lp, *ep; STM_TRACE(("%d: stm_listener_add(addr_string=\"%s\", addr=%#x, port=%u)\n", getpid(), addr_string, ntohl(addr), ntohs(port))); *new = 0; for (lp = stm.listeners, ep = lp + stm.num_listeners; lp < ep; lp++) if (lp->addr.sin_addr.s_addr == addr && lp->addr.sin_port == port) return lp; if (stm.num_listeners < STM_LISTENER_LIMIT) { lp->addr_string = apr_pstrdup(stm.pool, addr_string); lp->addr.sin_family = AF_INET; lp->addr.sin_port = port; lp->addr.sin_addr.s_addr = addr; lp->unique = addr != htonl(INADDR_ANY); stm.num_listeners++; *new = 1; return lp; } return NULL; } /* * Adapt the given ap-listener so that the given stm-listener can be * used instead. Returns nonzero on success, zero on failure. */ HIDDEN int stm_listener_usurp(stm_listener *listener, ap_listen_rec *alr) { int sd; STM_TRACE(("%d: stm_listener_usurp(listener=%p,#%d, alr=%p) share_count=%d\n", getpid(), listener, listener->id, alr, listener->share_count)); /* the listener must have a valid address at this point */ STM_ASSERT(listener->addr_string); listener->alr = alr; if (apr_get_os_sock(&sd, alr->sd) == APR_SUCCESS) { listener->sd = st_netfd_open_socket(sd); if (listener->sd != NULL) { /* serialize accepts only on shared listeners */ if (listener->share_count <= 1 || st_netfd_serialize_accept(listener->sd) == 0) { stm_disable_nagle(listener->sd); return 1; } else ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_listener_usurp: st_netfd_serialize_accept"); } else ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_listener_usurp: st_netfd_open_socket"); } else ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_listener_usurp: apr_get_os_sock"); return 0; } /* * Revert the given listener from STM control. */ HIDDEN void stm_listener_revert(stm_listener *listener) { STM_TRACE(("%d: stm_listener_revert(listener=%p,#%d)\n", getpid(), listener, listener->id)); st_netfd_free(listener->sd); listener->sd = NULL; } /*********************************************************************** * Threads */ /* * Initialize/reset the given thread which is bound to the given VP. */ HIDDEN void stm_thread_init(stm_thread *thread, stm_vp *vp) { STM_TRACE(("%d: stm_thread_init(thread=%p,#%d, vp=%p,#%d)\n", getpid(), thread, (int) (thread - vp->threads), vp, vp->id)); thread->id = (int) (thread - vp->threads); STM_ASSERT(thread->id >= 0 && thread->id < STM_ST_LIMIT); thread->health = STM_HEALTH_DEAD; thread->st = 0; thread->vp = vp; thread->listener = NULL; thread->connected = 0; thread->connections = stm.config.thread_connections; } /* * Attach the given listener to the given thread and start the given * thread running. Returns nonzero on success, zero on failure. */ HIDDEN int stm_thread_start(stm_thread *thread, stm_listener *listener) { STM_TRACE(("%d: stm_thread_start(thread=%p,#%d, listener=%p,#%d)\n", getpid(), thread, thread->id, listener, listener->id)); /* it would be bad to clobber a running thread */ STM_ASSERT(thread->health == STM_HEALTH_DEAD); thread->listener = listener; thread->st = st_thread_create(stm_thread_main, thread, 0, stm.config.stack_size); if (thread->st) { /* * Note it is impossible for the new thread to run until this * thread blocks. No race conditions on startup. */ thread->health = STM_HEALTH_ALIVE; return 1; } ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_thread_start: st_thread_create"); return 0; } /* * Return the thread currently running, or NULL if called by a * non-thread entity (e.g., the master process or the primordial VP * thread). */ HIDDEN stm_thread * stm_thread_self(void) { STM_TRACE(("%d: stm_thread_self()\n", getpid())); /* * We could walk through all of STs on all of the VPs in stm looking * for a match for st_thread_self() but a faster way is to use * thread-specific data. The catch with TSD is that the key must be * the same on all VPs, since we don't have a VP reference either. * Therefore the key must be allocated before any VP. */ return (stm_thread *) st_thread_getspecific(stm.self_key); } /* * Thread main function. Always returns NULL. arg is a pointer to the * thread. */ HIDDEN void * stm_thread_main(void *arg) { stm_thread *thread; stm_vp *vp; stm_listener *lp; long id; stm_scoreboard *sbp; apr_int32_t *sbtcp, *sblcp; apr_pool_t *conn_pool; STM_TRACE(("%d: => stm_thread_main(arg=%p)\n", getpid(), arg)); thread = (stm_thread *) arg; st_thread_setspecific(stm.self_key, thread); vp = thread->vp; lp = thread->listener; id = stm_id_make(vp->id, thread->id); sbp = vp->scoreboard; sbtcp = &sbp->thread_connections[thread->id]; sblcp = &sbp->listener_connections[lp->id]; sbp->thread_starts++; /* * Check health once before entering st_accept() to catch early stop * requests (see stm_thread_stop()). Even if this ST has been * st_interrupt()ed st_accept() will return EINTR only if there is * no connection pending. (ST I/O functions check for interrupts * only when about to block the thread.) */ if (thread->health == STM_HEALTH_ALIVE && apr_create_pool(&conn_pool, vp->pool) == APR_SUCCESS) { do { struct sockaddr_in addr; int addrlen; st_netfd_t nsd; /* get a connection */ addrlen = sizeof addr; nsd = st_accept(lp->sd, (struct sockaddr *) &addr, &addrlen, -1); if (nsd == NULL) { if (errno == EINTR) break; /* stm_thread_stop() called; terminate now */ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, "stm_thread_main: st_accept"); if (st_sleep(1) == -1 && errno == EINTR) break; continue; } /* * I have a connection, so keep stm_thread_stop() from * st_thread_interrupt()ing me now. */ thread->connected = 1; /* * This thread is no longer "spare." Start a replacement * spare thread if necessary and allowed. */ if (--lp->spare_threads < stm.config.min_spare_threads) { if (vp->num_threads < stm.config.max_threads) { if (stm_thread_start(vp->dead_threads[vp->num_threads], lp)) { vp->num_threads++; if (sbp->most_threads < vp->num_threads) sbp->most_threads = vp->num_threads; lp->spare_threads++; } } else if (!vp->max_threads_hit) { vp->max_threads_hit = 1; /* report once per VP */ ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "ran out of threads on VP %d; " "increase " STM_CMD_MAX_THREADS " and/or " STM_CMD_NUM_VPS, vp->pid); } } /* process the connection */ stm_thread_process_connection(lp, conn_pool, nsd, &addr, id); /* * Connection has been closed. Clean up and return thread * to spare state. */ apr_clear_pool(conn_pool); thread->connected = 0; lp->spare_threads++; /* update counters */ sbp->vp_connections++; /* per VP */ (*sbtcp)++; /* per thread */ (*sblcp)++; /* per listener */ if (vp->connections > 0 && --vp->connections == 0) { /* * This VP has processed as many connections as it's * allowed. Terminate the whole VP, including this * thread, immediately and gently. */ stm_vp_stop_self(vp); break; } if (thread->connections > 0 && --thread->connections == 0) { /* * This thread has processed as many consecutive * connections as it's allowed. Yield the VP to another * thread. */ st_usleep(0); thread->connections = stm.config.thread_connections; } /* check for stop requests, and for too many spare threads */ } while (thread->health == STM_HEALTH_ALIVE && lp->spare_threads <= stm.config.max_spare_threads); apr_destroy_pool(conn_pool); } /* this thread is dying */ lp->spare_threads--; vp->num_threads--; vp->dead_threads[vp->num_threads] = thread; sbp->thread_exits++; /* this thread is dead */ stm_thread_init(thread, vp); STM_TRACE(("%d: <= stm_thread_main(arg=%p)\n", getpid(), arg)); return NULL; } /* * Process a connection with a client. Calls back into the main server * to process requests, etc. On return, sd has been closed. */ HIDDEN void stm_thread_process_connection(const stm_listener *listener, apr_pool_t *conn_pool, st_netfd_t sd, const struct sockaddr_in *raddr, long id) { struct sockaddr_in laddr; const struct sockaddr_in *laddrp; STM_TRACE(("%d: stm_thread_process_connection(listener=%p,#%d, conn_pool=%p, sd=%p(%d), raddr=%s:%hu, id=%ld)\n", getpid(), listener, listener->id, conn_pool, sd, st_netfd_fileno(sd), inet_ntoa(raddr->sin_addr), ntohs(raddr->sin_port), id)); #if defined(IRIX) && IRIX >= 65 /* * TCP_NODELAY is inherited from the accepting socket in Irix 6.5 * and beyond. */ #else stm_disable_nagle(sd); #endif if (listener->unique) laddrp = &listener->addr; else { int addrlen; addrlen = sizeof laddr; if (getsockname(st_netfd_fileno(sd), &laddr, &addrlen) == 0) laddrp = &laddr; else { ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, "stm_thread_process_connection: getsockname"); laddrp = NULL; } } if (laddrp) { ap_iol *iolp; /* make all I/O on this socket use ST's I/O functions */ iolp = stiol_attach(sd); if (iolp) { BUFF *bp; conn_rec *conn; bp = ap_bcreate(conn_pool, B_RDWR); ap_bpush_iol(bp, iolp); conn = ap_new_connection(conn_pool, stm.server, bp, raddr, laddrp, id); ap_process_connection(conn); ap_lingering_close(conn); } else { ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, "stm_thread_process_connection: stiol_attach"); st_netfd_close(sd); } } else st_netfd_close(sd); } /* * Ask the given thread to terminate. Must be called only from within * the VP of the given thread. */ HIDDEN void stm_thread_stop(stm_thread *thread) { STM_TRACE(("%d: stm_thread_stop(thread=%p,#%d)\n", getpid(), thread, thread->id)); STM_ASSERT(getpid() == thread->vp->pid); if (thread->health == STM_HEALTH_ALIVE) { thread->health = STM_HEALTH_DYING; /* * break the thread out of st_accept() if necessary, and no * other I/O function */ if (!thread->connected) st_thread_interrupt(thread->st); } } /* * Return nonzero if the given thread has been terminated or asked to * terminate. */ HIDDEN int stm_thread_stopped(stm_thread *thread) { STM_TRACE(("%d: stm_thread_stopped(thread=%p,#%d)\n", getpid(), thread, thread->id)); return thread->health != STM_HEALTH_ALIVE; } /*********************************************************************** * VPs */ /* * Initialize/reset the given VP. Returns nonzero on success, zero on * failure. */ HIDDEN int stm_vp_init(stm_vp *vp) { int i; STM_TRACE(("%d: stm_vp_init(vp=%p,#%d)\n", getpid(), vp, (int) (vp - stm.vps))); vp->id = (int) (vp - stm.vps); STM_ASSERT(vp->id >= 0 && vp->id < STM_VP_LIMIT); vp->pid = -1; vp->health = STM_HEALTH_DEAD; vp->pool = NULL; vp->stop_pipe[0] = -1; vp->stop_pipe[1] = -1; /* leave vp->num_listeners and vp->listeners alone */ vp->connections = stm.config.vp_connections; vp->max_threads_hit = 0; vp->num_threads = 0; for (i = 0; i < STM_ST_LIMIT; i++) { stm_thread_init(&vp->threads[i], vp); vp->dead_threads[i] = &vp->threads[i]; } if (vp->scoreboard == NULL) { /* * create scoreboard even if !stm.config.connection_status * because we may want them after a restart and we keep other * stats in there */ vp->scoreboard = stm_scoreboard_new(vp->id); if (vp->scoreboard == NULL) return 0; } stm_scoreboard_init(vp->scoreboard); return 1; } /* * Initialize the listeners for the given VP from the stm.listeners and * stm.config.vp_listen tables. Returns nonzero on success, zero on * failure. */ HIDDEN int stm_vp_listen(stm_vp *vp) { int i; STM_TRACE(("%d: stm_vp_listen(vp=%p,#%d)\n", getpid(), vp, vp->id)); if (stm.config.vp_listen #ifdef STM_DEBUG && !stm.debug.one_process #endif ) { const stm_config_vp_listen *vlp; /* * VPListen directives were used. Each VP listens only to its * designated listeners. */ vlp = &stm.config.vp_listen[vp->id]; for (i = 0; i < vlp->num_listeners; i++) { vp->listeners[i] = vlp->listeners[i]; vp->listeners[i]->share_count++; } vp->num_listeners = i; while (i < STM_LISTENER_LIMIT) vp->listeners[i++] = NULL; if (vp->num_listeners < 1) { ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO, 0, stm.server, "no listeners for virtual processor %d", vp->id); return 0; } } else { /* * Listen directives were used, or no Listen or VPListen * directives were used at all. Each VP listens to all * listeners. */ vp->num_listeners = stm.num_listeners; for (i = 0; i < vp->num_listeners; i++) { vp->listeners[i] = &stm.listeners[i]; vp->listeners[i]->share_count++; } while (i < STM_LISTENER_LIMIT) vp->listeners[i++] = NULL; } /* this VP must have at least one listener at this point */ STM_ASSERT(vp->num_listeners > 0); return 1; } /* * Start the given VP running. Returns nonzero on success, zero on * failure. */ HIDDEN int stm_vp_start(stm_vp *vp) { pid_t pid; STM_TRACE(("%d: stm_vp_start(vp=%p,#%d)\n", getpid(), vp, vp->id)); /* it would be bad to clobber a running VP */ STM_ASSERT(vp->health == STM_HEALTH_DEAD); #ifdef STM_DEBUG if (stm.debug.one_process) { vp->pid = getpid(); stm_vp_bind(vp); stm_vp_main(vp); /*NOTREACHED*/ return 0; } #endif pid = fork(); switch (pid) { case -1: ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_vp_start: fork"); sleep(10); /* avoid thrashing the system */ return 0; case 0: vp->pid = getpid(); stm_vp_bind(vp); /* in case child runs first */ stm_vp_main(vp); /*NOTREACHED*/ exit(APEXIT_CHILDFATAL); break; default: vp->pid = pid; stm_vp_bind(vp); /* in case parent runs first */ vp->health = STM_HEALTH_ALIVE; break; } return 1; } /* * Bind the given VP to the CPU to which it should be bound, if any. */ #if defined(IRIX) && IRIX >= 50 # include #endif HIDDEN void stm_vp_bind(stm_vp *vp) { STM_TRACE(("%d: stm_vp_bind(vp=%p,#%d)\n", getpid(), vp, vp->id)); /* we need the VP's pid to bind it */ STM_ASSERT(vp->pid > 0); if (stm.config.vp_bind) { int cpu; cpu = stm.config.vp_bind[vp->id]; if (cpu >= 0 && vp->scoreboard->cpu != cpu) { #if defined(IRIX) && IRIX >= 50 /* only Irix 5.0 and beyond have sysmp(MP_MUSTRUN_PID) */ if (sysmp(MP_MUSTRUN_PID, cpu, vp->pid) != -1) vp->scoreboard->cpu = cpu; else ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, "cannot bind process %d to cpu %d: sysmp(MP_MUSTRUN_PID)", vp->pid, cpu); #else ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, stm.server, "don't know how to bind process %d to cpu %d", vp->pid, cpu); #endif } } } /* * Establish signal dispositions for VP processes. */ HIDDEN void stm_vp_signals(void) { STM_TRACE(("%d: stm_vp_signals()\n", getpid())); /* adjust signal dispositions this VP inherited from the master process */ stm_signal_default(SIGTERM); #ifdef SIGINT stm_signal_default(SIGINT); #endif stm_signal_catch(SIGHUP, stm_vp_onsig_stop); stm_signal_ignore(SIGWINCH); } /* * VP main function. Does not return. */ HIDDEN void stm_vp_main(stm_vp *vp) { st_netfd_t stop_fd; int i; STM_TRACE(("%d: => stm_vp_main(vp=%p,#%d)\n", getpid(), vp, vp->id)); /* * Set stm.num_vps to our VP index so asynchronous global functions * like stm_vp_onsig_stop() and stm_vp_onexit() have a handle on the * current VP. */ stm.num_vps = vp->id; vp->health = STM_HEALTH_ALIVE; vp->scoreboard->pid = vp->pid; vp->scoreboard->vp_start_time = apr_now(); vp->scoreboard->incarnations++; if (unixd_setup_child() != 0) exit(APEXIT_CHILDINIT); /* * Create the stop pipe, a safe means for this VP to signal * asynchronous events. */ if (pipe(vp->stop_pipe) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_vp_main: pipe"); exit(APEXIT_CHILDINIT); } stop_fd = st_netfd_open(vp->stop_pipe[0]); if (stop_fd == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server, "stm_vp_main: st_netfd_open"); exit(APEXIT_CHILDINIT); } /* * Create a pool that endures for the life of this VP, run child * init hooks on it, and use it to close both ends of the stop_pipe * when execing another program. */ apr_create_pool(&vp->pool, stm.pool); ap_child_init_hook(vp->pool, stm.server); apr_register_cleanup(vp->pool, vp, apr_null_cleanup, stm_vp_cleanup); atexit(stm_vp_onexit); /* Start the requested number of spare threads per listener. */ for (i = 0; i < vp->num_listeners && stm.state == STM_STATE_RUN; i++) { stm_listener *lp; int start_error; lp = vp->listeners[i]; start_error = 0; apr_snprintf(vp->scoreboard->listeners[lp->id], sizeof vp->scoreboard->listeners[lp->id], "%s:%u", lp->addr_string, ntohs(lp->addr.sin_port)); while (lp->spare_threads < stm.config.start_threads && vp->num_threads < stm.config.max_threads) { if (stm_thread_start(&vp->threads[vp->num_threads], lp)) { vp->num_threads++; lp->spare_threads++; } else { start_error = 1; break; } } if (vp->scoreboard->most_threads < vp->num_threads) vp->scoreboard->most_threads = vp->num_threads; if (lp->spare_threads < stm.config.start_threads) { /* * We didn't make all the threads we wanted to. Either we * exceeded the total number of threads per VP or a thread * failed to start. Warn, once, about the former condition. */ if (!start_error && !vp->max_threads_hit) { STM_ASSERT(vp->num_threads >= stm.config.max_threads); vp->max_threads_hit = 1; /* report once per VP */ ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "ran out of threads on VP %d; " "increase " STM_CMD_MAX_THREADS, vp->pid); } /* Also warn about too-few threads per listener. */ ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "started only %d out of %d thread(s) listening to %s on virtual processor %d", lp->spare_threads, stm.config.start_threads, vp->scoreboard->listeners[lp->id], vp->pid); } } stm_vp_signals(); if (stm.state != STM_STATE_RUN) { /* caught signal on parent's handler: do the right thing */ exit(APEXIT_CHILDINIT); } if (vp->num_threads > 0) { char c; /* * Block here for data on the pipe. This st_read() returns only * when the pipe has data on it (see stm_vp_stop_self()) and * that means it's time to shut down all the threads. */ st_read(stop_fd, &c, sizeof c, -1); for (i = 0; i < STM_ST_LIMIT; i++) stm_thread_stop(&vp->threads[i]); } /* this VP is dying */ st_netfd_close(stop_fd); close(vp->stop_pipe[1]); STM_TRACE(("%d: <= stm_vp_main(vp=%p,#%d)\n", getpid(), vp, vp->id)); st_thread_exit(NULL); /*NOTREACHED*/ } /* * Ask the given VP to terminate. Must be called only by the master * process. */ HIDDEN void stm_vp_stop(stm_vp *vp) { STM_TRACE(("%d: stm_vp_stop(vp=%p,#%d)\n", getpid(), vp, vp->id)); STM_ASSERT(stm.pid == getpid()); if (vp->health == STM_HEALTH_ALIVE) { vp->health = STM_HEALTH_DYING; STM_ASSERT(vp->pid > 0); kill(vp->pid, SIGHUP); } } /* * Ask the given VP to terminate. Must be called only from within the * given VP. */ HIDDEN void stm_vp_stop_self(stm_vp *vp) { char c; STM_TRACE(("%d: stm_vp_stop_self(vp=%p,#%d)\n", getpid(), vp, vp->id)); STM_ASSERT(vp->pid == getpid()); if (vp->health == STM_HEALTH_ALIVE) { vp->health = STM_HEALTH_DYING; write(vp->stop_pipe[1], &c, sizeof c); } } /* * Signal handler for a VP process: Terminate this VP. */ HIDDEN void stm_vp_onsig_stop(int sig) { STM_TRACE(("%d: stm_vp_onsig_stop(sig=%d)\n", getpid(), sig)); stm_vp_stop_self(&stm.vps[stm.num_vps]); } /* * Atexit handler for a VP process: Destroy the VP's pool. */ HIDDEN void stm_vp_onexit(void) { STM_TRACE(("%d: stm_vp_onexit()\n", getpid())); apr_destroy_pool(stm.vps[stm.num_vps].pool); } /* * Clean up the VP to prepare to exec another program. */ HIDDEN apr_status_t stm_vp_cleanup(void *arg) { stm_vp *vp; vp = (stm_vp *) arg; STM_TRACE(("%d: stm_vp_cleanup(vp=%p,#%d)\n", getpid(), vp, vp->id)); close(vp->stop_pipe[0]); close(vp->stop_pipe[1]); return APR_SUCCESS; } /*********************************************************************** * Main STM */ /* * Initialize/reset the STM MPM. Returns nonzero on success, zero on * failure. */ HIDDEN int stm_init(apr_pool_t *pool, server_rec *server) { int first; int i; ap_listen_rec *alp; STM_TRACE(("%d: stm_init(pool=%p, server=%p) #%d\n", getpid(), pool, server, stm.initialized)); /* verify all configuration parameters' bounds checks */ STM_ASSERT(stm.config.num_vps > 0 && stm.config.num_vps <= STM_VP_LIMIT); STM_ASSERT(stm.config.start_threads > 0 && stm.config.start_threads <= STM_ST_LIMIT); STM_ASSERT(stm.config.min_spare_threads > 0 && stm.config.min_spare_threads <= STM_ST_LIMIT); STM_ASSERT(stm.config.max_spare_threads > 0 && stm.config.max_spare_threads <= STM_ST_LIMIT); STM_ASSERT(stm.config.max_threads > 0 && stm.config.max_threads <= STM_ST_LIMIT); /* first is nonzero to initialize, zero to reset */ first = stm.initialized++ == 0; if (first) { int maxfds; stm.start_time = apr_now(); /* initialize the state threads library */ if (st_init() != 0) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, server, "cannot initialize state threads library"); return 0; } /* * Are there enough file descriptors for max_threads * simultaneous connections per VP? */ maxfds = st_getfdlimit() - 10; /* or so */ if (stm.config.max_threads > maxfds) { ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, server, STM_CMD_MAX_THREADS " %d exceeds " "number of file descriptors per process %d; " "decrease " STM_CMD_MAX_THREADS " and compensate " "by increasing " STM_CMD_NUM_VPS, stm.config.max_threads, maxfds); return 0; } st_timecache_set(1); } /* * Initialize/reset each member of stm not already initialized/reset * by stm_pre_config() and the config routines. */ stm.pool = pool; stm.server = server; stm.state = STM_STATE_RUN; /* * leave alone: * stm.initialized (incremented above) * stm.num_restarts (stm_pre_config()) * stm.pid (stm_pre_config()) * stm.config (stm_pre_config()) * stm.num_listeners (stm_pre_config()) * stm.listeners (stm_pre_config()) */ stm.num_vps = 0; for (i = 0; i < STM_VP_LIMIT; i++) if (!stm_vp_init(&stm.vps[i])) return 0; if (first && st_key_create(&stm.self_key, NULL) == -1) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, server, "stm_init: st_key_create"); return 0; } /* leave stm.debug alone (stm_pre_config()) */ /* establish AP listeners and convert them to STM listeners */ if (ap_listeners == NULL) { int new; /* * There were no Listen or VPListen directives so * ap_setup_listeners() will add a default AP listener (yecch). * Add a matching STM listener. */ STM_ASSERT(stm.num_listeners == 0); stm_listener_add(APR_ANYADDR, htonl(INADDR_ANY), htons(server->port), &new); STM_ASSERT(new); } STM_ASSERT(stm.num_listeners > 0); for (i = 0; i < stm.config.num_vps; i++) if (!stm_vp_listen(&stm.vps[i])) return 0; if (ap_setup_listeners(server) > 0) { for (alp = ap_listeners; alp; alp = alp->next) { char *addr_string; struct in_addr addr; stm_port port; stm_listener *lp, *ep; /* find the STM listener for the AP listener */ if (apr_get_local_ipaddr(&addr_string, alp->sd) != APR_SUCCESS || !inet_aton(addr_string, &addr)) addr.s_addr = htonl(INADDR_ANY); if (apr_get_local_port(&port, alp->sd) != APR_SUCCESS) port = server->port; port = htons(port); for (lp = stm.listeners, ep = lp + stm.num_listeners; lp < ep; lp++) { if (lp->addr.sin_addr.s_addr == addr.s_addr && lp->addr.sin_port == port) { if (stm_listener_usurp(lp, alp)) break; else { ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO, 0, server, "cannot initialize listen socket for %s:%u", addr_string, ntohs(port)); return 0; } } } /* * Every AP listener must have an STM listener (and vice * versa, which we check below). */ STM_ASSERT(lp < ep); } } else { ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO, 0, server, "no listening sockets available"); return 0; } if (stm.config.min_spare_threads > stm.config.max_spare_threads) stm.config.min_spare_threads = stm.config.max_spare_threads; /* * Warn if the number of threads per VP needed to satisfy all the * listeners exceeds the maximum. (Other MPM's measure * start/min-spare/max-spare threads per child process but STM * measures them per listener so these warnings may catch * configuration mistakes caused by the different semantics.) */ for (i = 0; i < stm.config.num_vps; i++) { int nl = stm.vps[i].num_listeners; if (nl * stm.config.start_threads > stm.config.max_threads) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, "too few threads: %d " STM_CMD_START_THREADS " for each of %d listener(s) on virtual processor %d exceeds limit of %d " STM_CMD_MAX_THREADS "; decrease " STM_CMD_START_THREADS " or increase " STM_CMD_MAX_THREADS, stm.config.start_threads, nl, i, stm.config.max_threads); if (nl * stm.config.min_spare_threads > stm.config.max_threads) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, "too few threads: %d " STM_CMD_MIN_SPARE_THREADS " for each of %d listener(s) on virtual processor %d exceeds limit of %d " STM_CMD_MAX_THREADS "; decrease " STM_CMD_MIN_SPARE_THREADS " or increase " STM_CMD_MAX_THREADS, stm.config.min_spare_threads, nl, i, stm.config.max_threads); if (nl * stm.config.max_spare_threads > stm.config.max_threads) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, "too few threads: %d " STM_CMD_MAX_SPARE_THREADS " for each of %d listener(s) on virtual processor %d exceeds limit of %d " STM_CMD_MAX_THREADS "; decrease " STM_CMD_MAX_SPARE_THREADS " or increase " STM_CMD_MAX_THREADS, stm.config.max_spare_threads, nl, i, stm.config.max_threads); } /* Warn about CPU- or listen-binding for unused VPs */ for (i = stm.config.num_vps; i < STM_VP_LIMIT; i++) { if (stm.config.vp_bind && stm.config.vp_bind[i] != -1) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, STM_CMD_VP_BIND " directive(s) for unused virtual processor %d ignored: " STM_CMD_NUM_VPS " is %d", i, stm.config.num_vps); if (stm.config.vp_listen && stm.config.vp_listen[i].num_listeners > 0) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, STM_CMD_VP_LISTEN " directive(s) for unused virtual processor %d ignored: " STM_CMD_NUM_VPS " is %d", i, stm.config.num_vps); } /* * Warn about unused listeners. May be redundant with above * warnings about VPListens for unused VPs but want to make sure the * user knows nobody is listening to a particular address. */ for (i = 0; i < stm.num_listeners; i++) { STM_ASSERT(stm.listeners[i].alr && stm.listeners[i].sd); if (stm.listeners[i].share_count == 0) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server, "no virtual processors serve %s:%u", stm.listeners[i].addr_string, ntohs(stm.listeners[i].addr.sin_port)); } return 1; } /* * Finalize the STM in preparation for server restart or shutdown. */ HIDDEN void stm_fini(void) { int i; STM_TRACE(("%d: stm_fini()\n", getpid())); for (i = 0; i < stm.num_listeners; i++) stm_listener_revert(&stm.listeners[i]); } /* * Establish signal dispositions for the master process (and embryonic * VP processes). */ HIDDEN void stm_signals(void) { STM_TRACE(("%d: stm_signals()\n", getpid())); #ifdef SIGSEGV stm_signal_core(SIGSEGV); #endif #ifdef SIGBUS stm_signal_core(SIGBUS); #endif #ifdef SIGABORT stm_signal_core(SIGABORT); #endif #ifdef SIGABRT stm_signal_core(SIGABRT); #endif #ifdef SIGILL stm_signal_core(SIGILL); #endif stm_signal_catch(SIGTERM, stm_onsig_shutdown); #ifdef SIGINT stm_signal_catch(SIGINT, stm_onsig_shutdown); #endif #ifdef SIGXCPU stm_signal_default(SIGXCPU); #endif #ifdef SIGXFSZ stm_signal_default(SIGXFSZ); #endif #ifdef SIGPIPE stm_signal_ignore(SIGPIPE); #endif stm_signal_catch(SIGHUP, stm_onsig_restart); stm_signal_catch(SIGWINCH, stm_onsig_restart); } /* * Master process main function. Returns when server restart or * shutdown has been requested. */ HIDDEN void stm_main(void) { STM_TRACE(("%d: => stm_main()\n", getpid())); stm_signals(); /* start VPs */ while (stm.state == STM_STATE_RUN && stm.num_vps < stm.config.num_vps && stm_vp_start(&stm.vps[stm.num_vps])) stm.num_vps++; if (stm.state == STM_STATE_RUN && stm.num_vps < stm.config.num_vps) ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, stm.server, "started only %d out of %d virtual processor(s)", stm.num_vps, stm.config.num_vps); /* wait for shutdown or restart, restarting VPs as needed */ if (stm.num_vps > 0) { while (stm.state == STM_STATE_RUN) { pid_t pid; int status; pid = wait(&status); if (pid > 0) stm_reap(pid, status); } } else stm.state = STM_STATE_SHUTDOWN; STM_TRACE(("%d: <= stm_main(), state %d\n", getpid(), stm.state)); } /* * Reap a child process. If the process was a VP, start a replacement VP. */ HIDDEN void stm_reap(pid_t pid, int status) { stm_grief grief; STM_TRACE(("%d: stm_reap(pid=%d, status=%#x)\n", getpid(), pid, status)); grief = stm_mourn(pid, status); if (grief != STM_GRIEF_FATAL) { int i; for (i = 0; i < STM_VP_LIMIT; i++) if (stm.vps[i].pid == pid) break; if (i < STM_VP_LIMIT) { /* this was a VP; finish killing it */ stm.num_vps--; stm_vp_init(&stm.vps[i]); /* replace it */ if (stm.num_vps < stm.config.num_vps && stm_vp_start(&stm.vps[i])) { stm.num_vps++; if (grief != STM_GRIEF_NORMAL) sleep(5); /* avoid thrashing */ } } } else stm.state = STM_STATE_SHUTDOWN; } /* * Evaluate the seriousness of a child process's death and log a warning if * appropriate. Returns: * STM_GRIEF_NORMAL if the death was expected (e.g., voluntary VP death * after processing its allowed number of connections) or irrelevent * (e.g., not a VP) * STM_GRIEF_FATAL if the death was catastrophic (e.g., VP botched an * assertion) and the server should terminate too * STM_GRIEF_SURPRISE if the death was neither expected nor fatal (e.g., * VP failed to initialize) */ HIDDEN stm_grief stm_mourn(pid_t pid, int status) { stm_grief grief; STM_TRACE(("%d: stm_mourn(pid=%d, status=%#x)\n", getpid(), pid, status)); grief = STM_GRIEF_NORMAL; if (WIFEXITED(status)) { int ev = WEXITSTATUS(status); switch (ev) { case 0: break; case APEXIT_CHILDFATAL: ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, stm.server, "child %d returned a fatal error; shutting down", pid); grief = STM_GRIEF_FATAL; break; case APEXIT_CHILDINIT: ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, stm.server, "child %d failed to initialize", pid); grief = STM_GRIEF_SURPRISE; break; default: ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, stm.server, "child %d exited with unexpected status %d", pid, ev); grief = STM_GRIEF_SURPRISE; break; } } else if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); switch (sig) { #ifdef STM_DEBUG case SIGABRT: ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, stm.server, "child %d aborted, possible assertion botch; shutting down", pid); grief = STM_GRIEF_FATAL; break; #endif case SIGTERM: case SIGHUP: case SIGUSR1: case SIGKILL: break; default: ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, stm.server, "child pid %d exit signal %d" #ifdef SYS_SIGLIST " (%s)" #endif , pid, sig #ifdef SYS_SIGLIST , (sig < NumSIG) ? SYS_SIGLIST[sig] : "?" #endif ); if (WCOREDUMP(status)) ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, stm.server, "-- possible core dump in %s", stm.config.core_dir); #ifdef STM_DEBUG grief = STM_GRIEF_FATAL; #else grief = STM_GRIEF_SURPRISE; #endif break; } } return grief; } /* * Wait a limited amount of time for all VPs to terminate. Kill them if * they take too long to terminate themselves. The caller must have * already commenced their termination. */ HIDDEN void stm_doomsday(void) { int tries; st_utime_t waittime; STM_TRACE(("%d: stm_doomsday()\n", getpid())); /* wait increasing intervals but not forever */ for (tries = 0, waittime = 10000; stm.num_vps > 0 && tries <= 5; tries++, waittime *= 4) { stm_vp *vp; st_usleep(waittime); /* see who's dead */ for (vp = stm.vps; vp < &stm.vps[STM_VP_LIMIT]; vp++) { if (vp->health != STM_HEALTH_DEAD) { pid_t pid; int status; STM_ASSERT(vp->pid > 0); pid = waitpid(vp->pid, &status, WNOHANG); if (pid == vp->pid || pid == -1) { if (pid > 0) stm_mourn(pid, status); vp->health = STM_HEALTH_DEAD; stm.num_vps--; /* * don't need to stm_vp_init(vp) because mpm is * exiting and will be reinitialized later if * necessary */ } else { switch (tries) { case 0: /* 10 ms since doomsday */ case 1: /* 50 ms since doomsday */ break; case 2: /* 210 ms since doomsday */ case 3: /* 850 ms since doomsday */ ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "waiting for process %d to die, trying SIGTERM", vp->pid); kill(vp->pid, SIGTERM); break; case 4: /* 3.4 sec since doomsday */ ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "waiting for process %d to die, trying SIGKILL", vp->pid); kill(vp->pid, SIGKILL); break; default: /* >= 14 sec since doomsday */ ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "could not make process %d die, giving up", vp->pid); break; } } } } } } /*********************************************************************** * Signal helper routines */ /* * Direct the given signal to the given handler in a platform-neutral * way. */ HIDDEN void stm_signal_catch(int sig, void (*handler)(int)) { #ifdef NO_USE_SIGACTION STM_TRACE(("%d: stm_signal_catch(sig=%d, handler=%p)\n", getpid(), sig, handler)); signal(sig, handler); #else struct sigaction sa; STM_TRACE(("%d: stm_signal_catch(sig=%d, handler=%p)\n", getpid(), sig, handler)); sa.sa_flags = 0; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); if (sigaction(sig, &sa, NULL) != 0) ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, "stm_signal_catch: sigaction(%d)", sig); #endif } /* * Set the given signal to its default disposition. */ HIDDEN void stm_signal_default(int sig) { stm_signal_catch(sig, SIG_DFL); } /* * Set the given signal to be ignored. */ HIDDEN void stm_signal_ignore(int sig) { stm_signal_catch(sig, SIG_IGN); } /* * Set the given signal to dump core in the designated core dump * directory. */ HIDDEN void stm_signal_core(int sig) { #ifdef NO_USE_SIGACTION STM_TRACE(("%d: stm_signal_core(sig=%d)\n", getpid(), sig)); signal(sig, stm_onsig_core); #else struct sigaction sa; STM_TRACE(("%d: stm_signal_core(sig=%d)\n", getpid(), sig)); # if defined(SA_ONESHOT) sa.sa_flags = SA_ONESHOT; # elif defined(SA_RESETHAND) sa.sa_flags = SA_RESETHAND; # else sa.sa_flags = 0; # endif sigemptyset(&sa.sa_mask); sa.sa_handler = stm_onsig_core; if (sigaction(sig, &sa, NULL) != 0) ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, "stm_signal_core: sigaction(%d)", sig); #endif } /*********************************************************************** * Signal handlers */ /* * Dump core in the designated core dump directory. */ HIDDEN void stm_onsig_core(int sig) { STM_TRACE(("%d: stm_onsig_core(sig=%d)\n", getpid(), sig)); if (stm.config.core_dir) chdir(stm.config.core_dir); stm_signal_default(sig); kill(getpid(), sig); } /* * Begin server restart. Only SIGWINCH uses graceful restart. */ HIDDEN void stm_onsig_restart(int sig) { STM_TRACE(("%d: stm_onsig_restart(sig=%d)\n", getpid(), sig)); stm.state = (sig == SIGWINCH) ? STM_STATE_GRACEFUL : STM_STATE_RESTART; } /* * Begin server shutdown. */ HIDDEN void stm_onsig_shutdown(int sig) { STM_TRACE(("%d: stm_onsig_shutdown(sig=%d)\n", getpid(), sig)); stm.state = STM_STATE_SHUTDOWN; } /*********************************************************************** * Miscellany */ /* * Convert the given VP and thread indices to a "connection identifier" * for mod_status. */ HIDDEN long stm_id_make(int v, int t) { STM_TRACE(("%d: stm_id_make(v=%d, t=%d)\n", getpid(), v, t)); STM_ASSERT(v >= 0 && v < STM_VP_LIMIT); STM_ASSERT(t >= 0 && t < STM_ST_LIMIT); return (long) v * STM_ST_LIMIT + t; } /* * Convert the given connection identifier into VP and thread indices, * if within bounds. Returns nonzero on success, zero on failure. */ HIDDEN int stm_id_break(long id, int *vp, int *tp) { int v, t; STM_TRACE(("%d: stm_id_break(id=%ld)\n", getpid(), id)); v = id / STM_ST_LIMIT; t = id % STM_ST_LIMIT; if (v >= 0 && v < STM_VP_LIMIT && t >= 0 && t < STM_ST_LIMIT) { *vp = v; *tp = t; return 1; } return 0; } /* * Disable the Nagle algorithm on a TCP socket. */ HIDDEN void stm_disable_nagle(st_netfd_t fd) { int on; STM_TRACE(("%d: stm_disable_nagle(fd=%p(%d))\n", getpid(), fd, st_netfd_fileno(fd))); on = 1; if (setsockopt(st_netfd_fileno(fd), IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof on) < 0) ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server, "stm_disable_nagle: setsockopt(%d, TCP_NODELAY)", st_netfd_fileno(fd)); } /*********************************************************************** * Exported API routines */ /* * Begin server shutdown. */ void ap_start_shutdown(void) { STM_TRACE(("%d: ap_start_shutdown() STM\n", getpid())); kill(stm.pid, SIGTERM); } /* * Begin server restart. */ void ap_start_restart(int graceful) { STM_TRACE(("%d: ap_start_restart(graceful=%d) STM\n", getpid(), graceful)); kill(stm.pid, graceful ? SIGWINCH : SIGHUP); } /* * Returns nonzero if the loop processing requests on a connection * should prematurely terminate. This is the actual usage which belies * the name of this predicate function. */ int ap_graceful_stop_signalled(void) { STM_TRACE(("%d: ap_graceful_stop_signalled() STM\n", getpid())); /* * This is called from a ST on a VP via ap_process_http_connection() * so we must ask our thread if it has been requested to stop. * Doesn't matter if the whole server is going down, just that this * thread is, so the server should abort the connection ASAP. */ return stm_thread_stopped(stm_thread_self()); } /* * Clear all state about the connection with the given ID. */ void ap_reset_connection_status(long id) { int v, t; STM_TRACE(("%d: ap_reset_connection_status(id=%ld) STM\n", getpid(), id)); if (!stm.config.connection_status) return; if (stm_id_break(id, &v, &t)) { stm_score *sp, *ep; /* We should be running on VP v but it's not strictly necessary */ for (sp = stm.vps[v].scoreboard->scores[t], ep = sp + STM_SCORE_LIMIT; sp < ep; sp++) sp->key[0] = 0; } else ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "ap_reset_connection_status (STM): bogus id %ld", id); } /* * Associate the given key/value state with the connection with the * given ID. Keys and values may be silently truncated if they exceed * STM_SCORE_KEY_SIZE or STM_SCORE_VALUE_SIZE respectively. If state * with the given key already exists for this connection the new value * replaces the old. */ void ap_update_connection_status(long id, const char *key, const char *value) { int v, t; STM_TRACE(("%d: ap_update_connection_status(id=%ld, key=\"%s\", value=\"%s\") STM\n", getpid(), id, key, value)); if (!stm.config.connection_status) return; if (stm_id_break(id, &v, &t)) { stm_score *sp, *ep; /* We should be running on VP v but it's not strictly necessary */ for (sp = stm.vps[v].scoreboard->scores[t], ep = sp + STM_SCORE_LIMIT; sp < ep; sp++) { if (sp->key[0] == 0) { apr_cpystrn(sp->key, key, sizeof sp->key); apr_cpystrn(sp->value, value, sizeof sp->value); break; } if (!strcmp(sp->key, key)) { apr_cpystrn(sp->value, value, sizeof sp->value); break; } } } else ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server, "ap_update_connection_status (STM): bogus id %ld", id); } /* * Return an array of status information. */ apr_array_header_t * ap_get_status_table(apr_pool_t *pool) { apr_array_header_t *ap; int asize, tsize, i; ap_status_table_row_t *srp; int total_connections, most_threads, listener_connections[STM_LISTENER_LIMIT]; STM_TRACE(("%d: ap_get_status_table(pool=%p)\n", getpid(), pool)); /* * Create an array large enough to hold: * - one entry per VP containing VP-specific status * - one entry per thread per VP containing connection-specific status * - one entry containing general status */ asize = 1; if (stm.config.connection_status) asize += stm.config.max_threads; asize *= stm.config.num_vps; asize++; ap = apr_make_array(pool, asize, sizeof (ap_status_table_row_t)); /* put the general status table first */ srp = apr_push_array(ap); total_connections = 0; most_threads = 0; memset(listener_connections, 0, sizeof listener_connections); for (i = 0; i < stm.config.num_vps; i++) { const stm_vp *vp; const stm_scoreboard *sbp; ap_status_table_row_t *vrp; char timebuf[32]; int j; vp = &stm.vps[i]; sbp = vp->scoreboard; /* * Create tables large enough to hold: * - nine data about VP-specific status * - one datum per listener about listener-specific status * - one datum per thread about connection-specific status */ tsize = 9 + vp->num_listeners + stm.config.max_threads; vrp = apr_push_array(ap); vrp->conn_id = -1000 - i; vrp->data = apr_make_table(pool, tsize); apr_ctime(timebuf, sbp->vp_start_time); apr_table_addn(vrp->data, "Process ID", apr_psprintf(pool, "%d", sbp->pid)); apr_table_addn(vrp->data, "Start time", apr_pstrdup(pool, timebuf)); apr_table_addn(vrp->data, "Incarnations", apr_psprintf(pool, "%d", sbp->incarnations)); apr_table_addn(vrp->data, "Bound to CPU", apr_psprintf(pool, "%d", sbp->cpu)); apr_table_addn(vrp->data, "Thread starts", apr_psprintf(pool, "%d", sbp->thread_starts)); apr_table_addn(vrp->data, "Thread exits", apr_psprintf(pool, "%d", sbp->thread_exits)); apr_table_addn(vrp->data, "Most threads used at once", apr_psprintf(pool, "%d", sbp->most_threads)); apr_table_addn(vrp->data, "Connections served, prior incarnations", apr_psprintf(pool, "%d", sbp->historic_vp_connections)); apr_table_addn(vrp->data, "Connections served, this incarnation", apr_psprintf(pool, "%d", sbp->vp_connections)); total_connections += sbp->historic_vp_connections + sbp->vp_connections; if (most_threads < sbp->most_threads) most_threads = sbp->most_threads; for (j = 0; j < vp->num_listeners; j++) { const stm_listener *lp; int lc; lp = vp->listeners[j]; STM_ASSERT(lp->id >= 0 && lp->id < STM_LISTENER_LIMIT); lc = sbp->listener_connections[lp->id]; apr_table_addn(vrp->data, apr_psprintf(pool, "Connections served for %s", sbp->listeners[lp->id]), apr_psprintf(pool, "%d", lc)); listener_connections[lp->id] += lc; } for (j = 0; j < stm.config.max_threads; j++) { const stm_score *sp; apr_table_addn(vrp->data, apr_psprintf(pool, "Connections served by thread %d", j), apr_psprintf(pool, "%d", sbp->thread_connections[j])); sp = sbp->scores[j]; if (stm.config.connection_status && sp->key[0]) { const stm_score *lep; ap_status_table_row_t *rp; for (lep = sp + STM_SCORE_LIMIT; --lep >= sp; ) if (lep->key[0]) break; rp = apr_push_array(ap); rp->conn_id = stm_id_make(i, j); rp->data = apr_make_table(pool, lep - sp + 1); while (sp <= lep) { apr_table_add(rp->data, sp->key, sp->value); sp++; } } } /* see below */ STM_ASSERT(apr_table_elts(vrp->data)->nelts <= tsize); } /* * Create a table large enough to hold: * - two data about general server status * - one datum per listener about listener-specific status */ tsize = 2 + stm.num_listeners; srp->conn_id = -1; srp->data = apr_make_table(pool, tsize); apr_table_addn(srp->data, "Total connections served", apr_psprintf(pool, "%d", total_connections)); apr_table_addn(srp->data, "Most threads used at once", apr_psprintf(pool, "%d", most_threads)); for (i = 0; i < stm.num_listeners; i++) apr_table_addn(srp->data, apr_psprintf(pool, "Total connections served for %s:%u", stm.listeners[i].addr_string, ntohs(stm.listeners[i].addr.sin_port)), apr_psprintf(pool, "%d", listener_connections[i])); STM_ASSERT(apr_table_elts(srp->data)->nelts <= tsize); /* see below */ /* * Not really worthy of an abort if botched, because arrays and * tables grow automatically, but good to check that we're * preallocating enough space, for speed. */ STM_ASSERT(ap->nelts <= asize); return ap; } /* * Run the already-configured STM MPM. Returns nonzero if the server * should exit, zero if it should reconfigure and restart. */ int ap_mpm_run(apr_pool_t *conf_pool, apr_pool_t *log_pool, server_rec *server) { int i; STM_TRACE(("%d: => ap_mpm_run(conf_pool=%p, log_pool=%p, server=%p) STM\n", getpid(), conf_pool, log_pool, server)); if (!stm_init(conf_pool, server)) { ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, errno, server, "STM MPM failed to initialize, shutting down"); return -1; } ap_log_pid(conf_pool, stm.config.pid_file); ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, "%s configured -- resuming normal operations", ap_get_server_version()); ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, server, "State Threaded MPM: %d virtual processor(s) with up to %d threads each", stm.config.num_vps, stm.config.max_threads); ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, server, "Server built: %s", ap_get_server_built()); stm_main(); STM_ASSERT(stm.state != STM_STATE_RUN); switch (stm.state) { case STM_STATE_SHUTDOWN: ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, "shutting down by request from signal"); for (i = 0; i < STM_VP_LIMIT; i++) stm_vp_stop(&stm.vps[i]); stm_doomsday(); if (unlink(stm.config.pid_file) == 0) ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, stm.server, "removed PID file %s (pid=%d)", stm.config.pid_file, getpid()); break; case STM_STATE_RESTART: ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, "restarting by request from signal"); stm_signal_ignore(SIGTERM); if (unixd_killpg(getpgrp(), SIGTERM) == -1) ap_log_error(APLOG_MARK, APLOG_WARNING, errno, server, "ap_mpm_run(STM): killpg"); stm_doomsday(); break; case STM_STATE_GRACEFUL: ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server, "gracefully restarting by request from signal"); for (i = 0; i < STM_VP_LIMIT; i++) stm_vp_stop(&stm.vps[i]); stm_doomsday(); break; } stm_fini(); STM_TRACE(("%d: <= ap_mpm_run(conf_pool=%p, log_pool=%p, server=%p) STM, shutdown=%d\n", getpid(), conf_pool, log_pool, server, stm.state == STM_STATE_SHUTDOWN)); return stm.state == STM_STATE_SHUTDOWN; }