/*
* 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;
}