| /* ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2000-2001 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 |
| * <http://www.apache.org/>. |
| * |
| * Portions of this software are based upon public domain software |
| * originally written at the National Center for Supercomputing Applications, |
| * University of Illinois, Urbana-Champaign. |
| */ |
| |
| /* Multi-process, multi-threaded MPM for OS/2 |
| * |
| * Server consists of |
| * - a main, parent process |
| * - a small, static number of child processes |
| * |
| * The parent process's job is to manage the child processes. This involves |
| * spawning children as required to ensure there are always ap_daemons_to_start |
| * processes accepting connections. |
| * |
| * Each child process consists of a a pool of worker threads and a |
| * main thread that accepts connections & passes them to the workers via |
| * a work queue. The worker thread pool is dynamic, managed by a maintanence |
| * thread so that the number of idle threads is kept between |
| * min_spare_threads & max_spare_threads. |
| * |
| */ |
| |
| /* |
| Todo list |
| - Fix log file clashing between child processes |
| - Enforce MaxClients somehow |
| - Catch thread exceptions & initiate graceful shutdown of child process |
| */ |
| #define CORE_PRIVATE |
| #define INCL_NOPMAPI |
| #define INCL_DOS |
| #define INCL_DOSERRORS |
| |
| #include "ap_config.h" |
| #include "httpd.h" |
| #include "mpm_default.h" |
| #include "http_main.h" |
| #include "http_log.h" |
| #include "http_config.h" |
| #include "http_core.h" /* for get_remote_host */ |
| #include "http_connection.h" |
| #include "mpm.h" |
| #include "ap_mpm.h" |
| #include "ap_listen.h" |
| #include "apr_portable.h" |
| #include "mpm_common.h" |
| #include "apr_strings.h" |
| #include <os2.h> |
| #include <process.h> |
| |
| server_rec *ap_server_conf; |
| static apr_pool_t *pconf = NULL; /* Pool for config stuff */ |
| static const char *ap_pid_fname=NULL; |
| |
| /* Config globals */ |
| static int one_process = 0; |
| static int ap_daemons_to_start = 0; |
| static int ap_thread_limit = 0; |
| static int ap_max_requests_per_child = 0; |
| int ap_min_spare_threads = 0; |
| int ap_max_spare_threads = 0; |
| |
| /* Keep track of a few interesting statistics */ |
| int ap_max_daemons_limit = -1; |
| |
| /* volatile just in case */ |
| static int volatile shutdown_pending; |
| static int volatile restart_pending; |
| static int volatile is_graceful = 0; |
| ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */ |
| static int is_parent_process=TRUE; |
| HMTX ap_mpm_accept_mutex = 0; |
| |
| /* An array of these is stored in a shared memory area for passing |
| * sockets from the parent to child processes |
| */ |
| typedef struct { |
| struct sockaddr_in name; |
| apr_os_sock_t listen_fd; |
| } listen_socket_t; |
| |
| typedef struct { |
| apr_os_file_t errorlog_fd; |
| apr_time_t restart_time; |
| HMTX accept_mutex; |
| listen_socket_t listeners[1]; |
| } parent_info_t; |
| |
| static char master_main(); |
| static void spawn_child(int slot); |
| void ap_mpm_child_main(apr_pool_t *pconf); |
| static void set_signals(); |
| |
| |
| int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) |
| { |
| char *listener_shm_name; |
| parent_info_t *parent_info; |
| ULONG rc; |
| pconf = _pconf; |
| ap_server_conf = s; |
| restart_pending = 0; |
| |
| DosSetMaxFH(ap_thread_limit * 2); |
| listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid()); |
| rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ); |
| is_parent_process = rc != 0; |
| ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid()); |
| |
| if (rc == 0) { |
| /* Child process */ |
| ap_listen_rec *lr; |
| int num_listeners = 0; |
| |
| apr_file_close(ap_server_conf->error_log); |
| apr_os_file_put(&ap_server_conf->error_log, &parent_info->errorlog_fd, pconf); |
| ap_restart_time = parent_info->restart_time; |
| ap_mpm_accept_mutex = parent_info->accept_mutex; |
| |
| /* Set up a default listener if necessary */ |
| if (ap_listeners == NULL) { |
| ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec)); |
| ap_listeners = lr; |
| apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC, |
| DEFAULT_HTTP_PORT, 0, s->process->pool); |
| apr_socket_create(&lr->sd, lr->bind_addr->sa.sin.sin_family, |
| SOCK_STREAM, s->process->pool); |
| } |
| |
| for (lr = ap_listeners; lr; lr = lr->next) { |
| apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf); |
| num_listeners++; |
| } |
| |
| DosFreeMem(parent_info); |
| |
| /* Do the work */ |
| ap_mpm_child_main(pconf); |
| |
| /* Outta here */ |
| return 1; |
| } |
| else { |
| /* Parent process */ |
| char restart; |
| is_parent_process = TRUE; |
| ap_log_pid(pconf, ap_pid_fname); |
| |
| if (ap_setup_listeners(ap_server_conf) < 1) { |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ALERT, 0, s, |
| "no listening sockets available, shutting down"); |
| return 1; |
| } |
| |
| restart = master_main(); |
| ++ap_my_generation; |
| ap_scoreboard_image->global.running_generation = ap_my_generation; |
| |
| if (!restart) { |
| const char *pidfile = ap_server_root_relative(pconf, ap_pid_fname); |
| |
| if (pidfile != NULL && remove(pidfile) == 0) { |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, APR_SUCCESS, |
| ap_server_conf, "removed PID file %s (pid=%d)", |
| pidfile, getpid()); |
| } |
| |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, ap_server_conf, |
| "caught SIGTERM, shutting down"); |
| return 1; |
| } |
| } /* Parent process */ |
| |
| return 0; /* Restart */ |
| } |
| |
| |
| |
| /* Main processing of the parent process |
| * returns TRUE if restarting |
| */ |
| static char master_main() |
| { |
| server_rec *s = ap_server_conf; |
| ap_listen_rec *lr; |
| parent_info_t *parent_info; |
| char *listener_shm_name; |
| int listener_num, num_listeners, slot; |
| ULONG rc; |
| |
| printf("%s \n", ap_get_server_version()); |
| set_signals(); |
| |
| if (ap_setup_listeners(ap_server_conf) < 1) { |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ALERT, 0, s, |
| "no listening sockets available, shutting down"); |
| return FALSE; |
| } |
| |
| /* Allocate a shared memory block for the array of listeners */ |
| for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) { |
| num_listeners++; |
| } |
| |
| listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid()); |
| rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name, |
| sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t), |
| PAG_READ|PAG_WRITE|PAG_COMMIT); |
| |
| if (rc) { |
| ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, |
| "failure allocating shared memory, shutting down"); |
| return FALSE; |
| } |
| |
| /* Store the listener sockets in the shared memory area for our children to see */ |
| for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) { |
| apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd); |
| } |
| |
| /* Create mutex to prevent multiple child processes from detecting |
| * a connection with apr_poll() |
| */ |
| |
| rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE); |
| |
| if (rc) { |
| ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, |
| "failure creating accept mutex, shutting down"); |
| return FALSE; |
| } |
| |
| ap_restart_time = apr_time_now(); |
| parent_info->restart_time = ap_restart_time; |
| parent_info->accept_mutex = ap_mpm_accept_mutex; |
| apr_os_file_get(&parent_info->errorlog_fd, s->error_log); |
| |
| /* Allocate shared memory for scoreboard */ |
| if (ap_scoreboard_image == NULL) { |
| rc = DosAllocSharedMem((PPVOID)&ap_scoreboard_image, ap_scoreboard_fname, |
| sizeof(scoreboard), PAG_COMMIT|PAG_READ|PAG_WRITE); |
| |
| if (rc) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, |
| "unable to allocate shared memory for scoreboard , exiting"); |
| return FALSE; |
| } |
| |
| memset(ap_scoreboard_image, 0, sizeof(scoreboard)); |
| } |
| |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, ap_server_conf, |
| "%s configured -- resuming normal operations", |
| ap_get_server_version()); |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, ap_server_conf, |
| "Server built: %s", ap_get_server_built()); |
| |
| if (one_process) { |
| ap_scoreboard_image->parent[0].pid = getpid(); |
| ap_mpm_child_main(pconf); |
| return FALSE; |
| } |
| |
| while (!restart_pending && !shutdown_pending) { |
| RESULTCODES proc_rc; |
| PID child_pid; |
| int active_children = 0; |
| |
| /* Count number of active children */ |
| for (slot=0; slot < HARD_SERVER_LIMIT; slot++) { |
| active_children += ap_scoreboard_image->parent[slot].pid != 0 && |
| !ap_scoreboard_image->parent[slot].quiescing; |
| } |
| |
| /* Spawn children if needed */ |
| for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) { |
| if (ap_scoreboard_image->parent[slot].pid == 0) { |
| spawn_child(slot); |
| active_children++; |
| } |
| } |
| |
| rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0); |
| |
| if (rc == 0) { |
| /* A child has terminated, remove its scoreboard entry & terminate if necessary */ |
| for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++); |
| |
| if (slot < HARD_SERVER_LIMIT) { |
| ap_scoreboard_image->parent[slot].pid = 0; |
| ap_scoreboard_image->parent[slot].quiescing = 0; |
| |
| if (proc_rc.codeTerminate == TC_EXIT) { |
| /* Child terminated normally, check its exit code and |
| * terminate server if child indicates a fatal error |
| */ |
| if (proc_rc.codeResult == APEXIT_CHILDFATAL) |
| break; |
| } |
| } |
| } else if (rc == ERROR_CHILD_NOT_COMPLETE) { |
| /* No child exited, lets sleep for a while.... */ |
| apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); |
| } |
| } |
| |
| /* Signal children to shut down, either gracefully or immediately */ |
| for (slot=0; slot<HARD_SERVER_LIMIT; slot++) { |
| kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM); |
| } |
| |
| DosFreeMem(parent_info); |
| return restart_pending; |
| } |
| |
| |
| |
| static void spawn_child(int slot) |
| { |
| PPIB ppib; |
| PTIB ptib; |
| char fail_module[100]; |
| char progname[CCHMAXPATH]; |
| RESULTCODES proc_rc; |
| ULONG rc; |
| |
| ap_scoreboard_image->parent[slot].generation = ap_my_generation; |
| DosGetInfoBlocks(&ptib, &ppib); |
| DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname); |
| rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT, |
| ppib->pib_pchcmd, NULL, &proc_rc, progname); |
| |
| if (rc) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, |
| "error spawning child, slot %d", slot); |
| } |
| |
| if (ap_max_daemons_limit < slot) { |
| ap_max_daemons_limit = slot; |
| } |
| |
| ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate; |
| } |
| |
| |
| |
| /* Signal handling routines */ |
| |
| static void sig_term(int sig) |
| { |
| shutdown_pending = 1; |
| } |
| |
| |
| |
| static void sig_restart(int sig) |
| { |
| if (sig == SIGUSR1) { |
| is_graceful = 1; |
| } |
| |
| restart_pending = 1; |
| } |
| |
| |
| |
| static void set_signals() |
| { |
| struct sigaction sa; |
| |
| sigemptyset(&sa.sa_mask); |
| sa.sa_flags = 0; |
| sa.sa_handler = sig_term; |
| |
| if (sigaction(SIGTERM, &sa, NULL) < 0) |
| ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)"); |
| |
| if (sigaction(SIGINT, &sa, NULL) < 0) |
| ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)"); |
| |
| sa.sa_handler = sig_restart; |
| |
| if (sigaction(SIGHUP, &sa, NULL) < 0) |
| ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)"); |
| if (sigaction(SIGUSR1, &sa, NULL) < 0) |
| ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGUSR1)"); |
| } |
| |
| |
| |
| /* Enquiry functions used get MPM status info */ |
| |
| AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) |
| { |
| switch (query_code) { |
| case AP_MPMQ_MAX_DAEMON_USED: |
| *result = ap_max_daemons_limit; |
| return APR_SUCCESS; |
| case AP_MPMQ_IS_THREADED: |
| *result = AP_MPMQ_DYNAMIC; |
| return APR_SUCCESS; |
| case AP_MPMQ_IS_FORKED: |
| *result = AP_MPMQ_NOT_SUPPORTED; |
| return APR_SUCCESS; |
| case AP_MPMQ_HARD_LIMIT_DAEMONS: |
| *result = HARD_SERVER_LIMIT; |
| return APR_SUCCESS; |
| case AP_MPMQ_HARD_LIMIT_THREADS: |
| *result = HARD_THREAD_LIMIT; |
| return APR_SUCCESS; |
| case AP_MPMQ_MIN_SPARE_DEAMONS: |
| *result = 0; |
| return APR_SUCCESS; |
| case AP_MPMQ_MAX_SPARE_DAEMONS: |
| *result = 0; |
| return APR_SUCCESS; |
| case AP_MPMQ_MAX_REQUESTS_DEAMON: |
| *result = ap_max_requests_per_child; |
| return APR_SUCCESS; |
| } |
| return APR_ENOTIMPL; |
| } |
| |
| |
| |
| int ap_graceful_stop_signalled(void) |
| { |
| return is_graceful; |
| } |
| |
| |
| |
| /* Configuration handling stuff */ |
| |
| static void mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) |
| { |
| one_process = !!ap_exists_config_define("ONE_PROCESS"); |
| is_graceful = 0; |
| ap_listen_pre_config(); |
| ap_daemons_to_start = DEFAULT_START_DAEMON; |
| ap_thread_limit = HARD_THREAD_LIMIT; |
| ap_pid_fname = DEFAULT_PIDLOG; |
| ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD; |
| ap_extended_status = 0; |
| ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD; |
| ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD; |
| } |
| |
| |
| |
| static void mpmt_os2_hooks(apr_pool_t *p) |
| { |
| ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE); |
| } |
| |
| |
| |
| static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg) |
| { |
| const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); |
| |
| if (err != NULL) { |
| return err; |
| } |
| |
| ap_daemons_to_start = atoi(arg); |
| return NULL; |
| } |
| |
| |
| |
| static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, |
| const char *arg) |
| { |
| const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); |
| |
| if (err != NULL) { |
| return err; |
| } |
| |
| ap_min_spare_threads = atoi(arg); |
| |
| if (ap_min_spare_threads <= 0) { |
| ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, |
| "WARNING: detected MinSpareThreads set to non-positive."); |
| ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, |
| "Resetting to 1 to avoid almost certain Apache failure."); |
| ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, |
| "Please read the documentation."); |
| ap_min_spare_threads = 1; |
| } |
| |
| return NULL; |
| } |
| |
| |
| |
| static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, |
| const char *arg) |
| { |
| const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); |
| |
| if (err != NULL) { |
| return err; |
| } |
| |
| ap_max_spare_threads = atoi(arg); |
| return NULL; |
| } |
| |
| |
| |
| static const command_rec mpmt_os2_cmds[] = { |
| LISTEN_COMMANDS |
| AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF, |
| "Number of child processes launched at server startup" ), |
| AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, |
| "Minimum number of idle children, to handle request spikes"), |
| AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, |
| "Maximum number of idle children"), |
| { NULL } |
| }; |
| |
| module AP_MODULE_DECLARE_DATA mpm_mpmt_os2_module = { |
| MPM20_MODULE_STUFF, |
| NULL, /* hook to run before apache parses args */ |
| NULL, /* create per-directory config structure */ |
| NULL, /* merge per-directory config structures */ |
| NULL, /* create per-server config structure */ |
| NULL, /* merge per-server config structures */ |
| mpmt_os2_cmds, /* command apr_table_t */ |
| mpmt_os2_hooks, /* register_hooks */ |
| }; |