diff options
Diffstat (limited to 'server/mpm/mpmt_os2')
-rw-r--r-- | server/mpm/mpmt_os2/Makefile.in | 1 | ||||
-rw-r--r-- | server/mpm/mpmt_os2/config.m4 | 10 | ||||
-rw-r--r-- | server/mpm/mpmt_os2/config5.m4 | 3 | ||||
-rw-r--r-- | server/mpm/mpmt_os2/mpm_default.h | 57 | ||||
-rw-r--r-- | server/mpm/mpmt_os2/mpmt_os2.c | 614 | ||||
-rw-r--r-- | server/mpm/mpmt_os2/mpmt_os2_child.c | 490 |
6 files changed, 1175 insertions, 0 deletions
diff --git a/server/mpm/mpmt_os2/Makefile.in b/server/mpm/mpmt_os2/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/mpmt_os2/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/mpmt_os2/config.m4 b/server/mpm/mpmt_os2/config.m4 new file mode 100644 index 0000000..9a29903 --- /dev/null +++ b/server/mpm/mpmt_os2/config.m4 @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(if mpmt_os2 MPM supports this platform) +case $host in + *os2-emx*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(mpmt_os2, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/mpmt_os2/config5.m4 b/server/mpm/mpmt_os2/config5.m4 new file mode 100644 index 0000000..c74f145 --- /dev/null +++ b/server/mpm/mpmt_os2/config5.m4 @@ -0,0 +1,3 @@ +APACHE_MPM_MODULE(mpmt_os2, $enable_mpm_mpmt_os2, mpmt_os2.lo mpmt_os2_child.lo,[ + APR_ADDTO(CFLAGS,-Zmt) +]) diff --git a/server/mpm/mpmt_os2/mpm_default.h b/server/mpm/mpmt_os2/mpm_default.h new file mode 100644 index 0000000..36ba944 --- /dev/null +++ b/server/mpm/mpmt_os2/mpm_default.h @@ -0,0 +1,57 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file mpmt_os2/mpm_default.h + * @brief os2 MPM defaults + * + * @defgroup APACHE_MPM_OS2 OS/2 MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Number of servers processes to spawn off by default + */ +#ifndef DEFAULT_START_DAEMON +#define DEFAULT_START_DAEMON 2 +#endif + +/* Maximum number of *free* server threads --- more than this, and + * they will die off. + */ + +#ifndef DEFAULT_MAX_SPARE_THREAD +#define DEFAULT_MAX_SPARE_THREAD 10 +#endif + +/* Minimum --- fewer than this, and more will be created */ + +#ifndef DEFAULT_MIN_SPARE_THREAD +#define DEFAULT_MIN_SPARE_THREAD 5 +#endif + +/* + * Interval, in microseconds, between scoreboard maintenance. + */ +#ifndef SCOREBOARD_MAINTENANCE_INTERVAL +#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000 +#endif + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/mpmt_os2/mpmt_os2.c b/server/mpm/mpmt_os2/mpmt_os2.c new file mode 100644 index 0000000..b3adb03 --- /dev/null +++ b/server/mpm/mpmt_os2/mpmt_os2.c @@ -0,0 +1,614 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Multi-process, multi-threaded MPM for OS/2 + * + * Server consists of + * - a main, parent process + * - a small, static number of child processes + * + * The parent process's job is to manage the child processes. This involves + * spawning children as required to ensure there are always ap_daemons_to_start + * processes accepting connections. + * + * Each child process consists of a pool of worker threads and a + * main thread that accepts connections & passes them to the workers via + * a work queue. The worker thread pool is dynamic, managed by a maintenance + * thread so that the number of idle threads is kept between + * min_spare_threads & max_spare_threads. + * + */ + +/* + Todo list + - Enforce MaxRequestWorkers somehow +*/ +#define INCL_NOPMAPI +#define INCL_DOS +#define INCL_DOSERRORS + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "apr_portable.h" +#include "mpm_common.h" +#include "scoreboard.h" +#include "apr_strings.h" +#include <os2.h> +#include <process.h> + +/* We don't need many processes, + * they're only for redundancy in the event of a crash + */ +#define HARD_SERVER_LIMIT 10 + +/* Limit on the total number of threads per process + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 256 +#endif + +server_rec *ap_server_conf; +static apr_pool_t *pconf = NULL; /* Pool for config stuff */ + +/* Config globals */ +static int one_process = 0; +static int ap_daemons_to_start = 0; +static int ap_thread_limit = 0; +int ap_min_spare_threads = 0; +int ap_max_spare_threads = 0; + +/* Keep track of a few interesting statistics */ +int ap_max_daemons_limit = 0; + +/* volatile just in case */ +static int volatile shutdown_pending; +static int volatile restart_pending; +static int volatile is_graceful = 0; +ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */ +static int is_parent_process=TRUE; +HMTX ap_mpm_accept_mutex = 0; + +/* An array of these is stored in a shared memory area for passing + * sockets from the parent to child processes + */ +typedef struct { + struct sockaddr_in name; + apr_os_sock_t listen_fd; +} listen_socket_t; + +typedef struct { + HMTX accept_mutex; + listen_socket_t listeners[1]; +} parent_info_t; + +static int master_main(); +static void spawn_child(int slot); +void ap_mpm_child_main(apr_pool_t *pconf); +static void set_signals(); + + +static int mpmt_os2_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +{ + char *listener_shm_name; + parent_info_t *parent_info; + ULONG rc; + pconf = _pconf; + ap_server_conf = s; + restart_pending = 0; + + DosSetMaxFH(ap_thread_limit * 2); + listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid()); + rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ); + is_parent_process = rc != 0; + ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid()); + + if (rc == 0) { + /* Child process */ + ap_listen_rec *lr; + int num_listeners = 0; + + ap_mpm_accept_mutex = parent_info->accept_mutex; + + /* Set up a default listener if necessary */ + if (ap_listeners == NULL) { + ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec)); + ap_listeners = lr; + apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC, + DEFAULT_HTTP_PORT, 0, s->process->pool); + apr_socket_create(&lr->sd, lr->bind_addr->family, + SOCK_STREAM, 0, s->process->pool); + } + + for (lr = ap_listeners; lr; lr = lr->next) { + apr_sockaddr_t *sa; + apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf); + apr_socket_addr_get(&sa, APR_LOCAL, lr->sd); + num_listeners++; + } + + DosFreeMem(parent_info); + + /* Do the work */ + ap_mpm_child_main(pconf); + + /* Outta here */ + return DONE; + } + else { + /* Parent process */ + int rc; + is_parent_process = TRUE; + + if (ap_setup_listeners(ap_server_conf) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00200) + "no listening sockets available, shutting down"); + return !OK; + } + + ap_log_pid(pconf, ap_pid_fname); + + rc = master_main(); + ++ap_my_generation; + ap_scoreboard_image->global->running_generation = ap_my_generation; + + if (rc != OK) { + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00201) + "caught %s, shutting down", + (rc == DONE) ? "SIGTERM" : "error"); + return rc; + } + } /* Parent process */ + + return OK; /* Restart */ +} + + + +/* Main processing of the parent process + * returns TRUE if restarting + */ +static int master_main() +{ + server_rec *s = ap_server_conf; + ap_listen_rec *lr; + parent_info_t *parent_info; + char *listener_shm_name; + int listener_num, num_listeners, slot; + ULONG rc; + + printf("%s \n", ap_get_server_description()); + set_signals(); + + if (ap_setup_listeners(ap_server_conf) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00202) + "no listening sockets available, shutting down"); + return !OK; + } + + /* Allocate a shared memory block for the array of listeners */ + for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) { + num_listeners++; + } + + listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid()); + rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name, + sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t), + PAG_READ|PAG_WRITE|PAG_COMMIT); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00203) + "failure allocating shared memory, shutting down"); + return !OK; + } + + /* Store the listener sockets in the shared memory area for our children to see */ + for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) { + apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd); + } + + /* Create mutex to prevent multiple child processes from detecting + * a connection with apr_poll() + */ + + rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00204) + "failure creating accept mutex, shutting down"); + return !OK; + } + + parent_info->accept_mutex = ap_mpm_accept_mutex; + + /* Allocate shared memory for scoreboard */ + if (ap_scoreboard_image == NULL) { + void *sb_mem; + rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname, + ap_calc_scoreboard_size(), + PAG_COMMIT|PAG_READ|PAG_WRITE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00205) + "unable to allocate shared memory for scoreboard , exiting"); + return !OK; + } + + ap_init_scoreboard(sb_mem); + } + + ap_scoreboard_image->global->restart_time = apr_time_now(); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00206) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00207) + "Server built: %s", ap_get_server_built()); + if (one_process) { + ap_scoreboard_image->parent[0].pid = getpid(); + ap_mpm_child_main(pconf); + return DONE; + } + + while (!restart_pending && !shutdown_pending) { + RESULTCODES proc_rc; + PID child_pid; + int active_children = 0; + + /* Count number of active children */ + for (slot=0; slot < HARD_SERVER_LIMIT; slot++) { + active_children += ap_scoreboard_image->parent[slot].pid != 0 && + !ap_scoreboard_image->parent[slot].quiescing; + } + + /* Spawn children if needed */ + for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) { + if (ap_scoreboard_image->parent[slot].pid == 0) { + spawn_child(slot); + active_children++; + } + } + + rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0); + + if (rc == 0) { + /* A child has terminated, remove its scoreboard entry & terminate if necessary */ + for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++); + + if (slot < HARD_SERVER_LIMIT) { + ap_scoreboard_image->parent[slot].pid = 0; + ap_scoreboard_image->parent[slot].quiescing = 0; + + if (proc_rc.codeTerminate == TC_EXIT) { + /* Child terminated normally, check its exit code and + * terminate server if child indicates a fatal error + */ + if (proc_rc.codeResult == APEXIT_CHILDFATAL) + break; + } + } + } else if (rc == ERROR_CHILD_NOT_COMPLETE) { + /* No child exited, lets sleep for a while.... */ + apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); + } + } + + /* Signal children to shut down, either gracefully or immediately */ + for (slot=0; slot<HARD_SERVER_LIMIT; slot++) { + kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM); + } + + DosFreeMem(parent_info); + return restart_pending ? OK : DONE; +} + + + +static void spawn_child(int slot) +{ + PPIB ppib; + PTIB ptib; + char fail_module[100]; + char progname[CCHMAXPATH]; + RESULTCODES proc_rc; + ULONG rc; + + ap_scoreboard_image->parent[slot].generation = ap_my_generation; + DosGetInfoBlocks(&ptib, &ppib); + DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname); + rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT, + ppib->pib_pchcmd, NULL, &proc_rc, progname); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00208) + "error spawning child, slot %d", slot); + } + + if (slot + 1 > ap_max_daemons_limit) { + ap_max_daemons_limit = slot + 1; + } + + ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate; +} + + + +/* Signal handling routines */ + +static void sig_term(int sig) +{ + shutdown_pending = 1; + signal(SIGTERM, SIG_DFL); +} + + + +static void sig_restart(int sig) +{ + if (sig == SIGUSR1) { + is_graceful = 1; + } + + restart_pending = 1; +} + + + +static void set_signals() +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_term; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00209) "sigaction(SIGTERM)"); + + if (sigaction(SIGINT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00210) "sigaction(SIGINT)"); + + sa.sa_handler = sig_restart; + + if (sigaction(SIGHUP, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00211) "sigaction(SIGHUP)"); + if (sigaction(SIGUSR1, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00212) "sigaction(SIGUSR1)"); +} + + + +/* Enquiry functions used get MPM status info */ + +static apr_status_t mpmt_os2_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = ap_max_daemons_limit; + break; + + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_DYNAMIC; + break; + + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = HARD_THREAD_LIMIT; + break; + + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + + case AP_MPMQ_GENERATION: + *result = ap_my_generation; + break; + + default: + *rv = APR_ENOTIMPL; + break; + } + + return OK; +} + + + + +static const char *mpmt_os2_get_name(void) +{ + return "mpmt_os2"; +} + + + + +/* Configuration handling stuff */ + +static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + one_process = ap_exists_config_define("ONE_PROCESS") || + ap_exists_config_define("DEBUG"); + is_graceful = 0; + ap_listen_pre_config(); + ap_daemons_to_start = DEFAULT_START_DAEMON; + ap_thread_limit = HARD_THREAD_LIMIT; + ap_extended_status = 0; + ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD; + ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD; + ap_sys_privileges_handlers(1); + + return OK; +} + + + +static int mpmt_os2_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + static int restart_num = 0; + int startup = 0; + + /* we want this only the first time around */ + if (restart_num++ == 0) { + startup = 1; + } + + if (ap_daemons_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00213) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00214) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (ap_min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00215) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1 to avoid almost certain server failure. " + "Please read the documentation.", ap_min_spare_threads); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00216) + "MinSpareThreads of %d not allowed, increasing to 1", + ap_min_spare_threads); + } + ap_min_spare_threads = 1; + } + + return OK; +} + + + +static void mpmt_os2_hooks(apr_pool_t *p) +{ + ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(mpmt_os2_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(mpmt_os2_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(mpmt_os2_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(mpmt_os2_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + + + +static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_daemons_to_start = atoi(arg); + return NULL; +} + + + +static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_min_spare_threads = atoi(arg); + return NULL; +} + + + +static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + ap_max_spare_threads = atoi(arg); + return NULL; +} + + + +static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg) +{ + return NULL; +} + + + +static const command_rec mpmt_os2_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF, + "Number of child processes launched at server startup" ), +AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF, + "Minimum number of idle children, to handle request spikes"), +AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF, + "Maximum number of idle children"), +AP_INIT_TAKE1("User", ignore_cmd, NULL, RSRC_CONF, + "Not applicable on this platform"), +AP_INIT_TAKE1("Group", ignore_cmd, NULL, RSRC_CONF, + "Not applicable on this platform"), +AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \ + "Not applicable on this platform"), +{ NULL } +}; + +AP_DECLARE_MODULE(mpm_mpmt_os2) = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + mpmt_os2_cmds, /* command apr_table_t */ + mpmt_os2_hooks, /* register_hooks */ +}; diff --git a/server/mpm/mpmt_os2/mpmt_os2_child.c b/server/mpm/mpmt_os2/mpmt_os2_child.c new file mode 100644 index 0000000..f405cd2 --- /dev/null +++ b/server/mpm/mpmt_os2/mpmt_os2_child.c @@ -0,0 +1,490 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define INCL_NOPMAPI +#define INCL_DOS +#define INCL_DOSERRORS + +#include "ap_config.h" +#include "httpd.h" +#include "mpm_default.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "scoreboard.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "apr_portable.h" +#include "apr_poll.h" +#include "mpm_common.h" +#include "apr_strings.h" +#include <os2.h> +#include <process.h> + +APLOG_USE_MODULE(mpm_mpmt_os2); + +/* XXXXXX move these to header file private to this MPM */ + +/* We don't need many processes, + * they're only for redundancy in the event of a crash + */ +#define HARD_SERVER_LIMIT 10 + +/* Limit on the total number of threads per process + */ +#ifndef HARD_THREAD_LIMIT +#define HARD_THREAD_LIMIT 256 +#endif + +#define ID_FROM_CHILD_THREAD(c, t) ((c * HARD_THREAD_LIMIT) + t) + +typedef struct { + apr_pool_t *pconn; + apr_socket_t *conn_sd; +} worker_args_t; + +#define WORKTYPE_CONN 0 +#define WORKTYPE_EXIT 1 + +static apr_pool_t *pchild = NULL; +static int child_slot; +static int shutdown_pending = 0; +extern int ap_my_generation; +static int volatile is_graceful = 1; +HEV shutdown_event; /* signaled when this child is shutting down */ + +/* grab some MPM globals */ +extern int ap_min_spare_threads; +extern int ap_max_spare_threads; +extern HMTX ap_mpm_accept_mutex; + +static void worker_main(void *vpArg); +static void clean_child_exit(int code); +static void set_signals(); +static void server_maintenance(void *vpArg); + + +static void clean_child_exit(int code) +{ + if (pchild) { + apr_pool_destroy(pchild); + } + + exit(code); +} + + + +void ap_mpm_child_main(apr_pool_t *pconf) +{ + ap_listen_rec *lr = NULL; + int requests_this_child = 0; + int rv = 0; + unsigned long ulTimes; + int my_pid = getpid(); + ULONG rc, c; + HQUEUE workq; + apr_pollset_t *pollset; + int num_listeners; + TID server_maint_tid; + void *sb_mem; + + /* Stop Ctrl-C/Ctrl-Break signals going to child processes */ + DosSetSignalExceptionFocus(0, &ulTimes); + set_signals(); + + /* Create pool for child */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + + /* Create an event semaphore used to trigger other threads to shutdown */ + rc = DosCreateEventSem(NULL, &shutdown_event, 0, FALSE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00189) + "unable to create shutdown semaphore, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Gain access to the scoreboard. */ + rc = DosGetNamedSharedMem(&sb_mem, ap_scoreboard_fname, + PAG_READ|PAG_WRITE); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00190) + "scoreboard not readable in child, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_calc_scoreboard_size(); + ap_init_scoreboard(sb_mem); + + /* Gain access to the accpet mutex */ + rc = DosOpenMutexSem(NULL, &ap_mpm_accept_mutex); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00191) + "accept mutex couldn't be accessed in child, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Find our pid in the scoreboard so we know what slot our parent allocated us */ + for (child_slot = 0; ap_scoreboard_image->parent[child_slot].pid != my_pid && child_slot < HARD_SERVER_LIMIT; child_slot++); + + if (child_slot == HARD_SERVER_LIMIT) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00192) + "child pid not found in scoreboard, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + ap_my_generation = ap_scoreboard_image->parent[child_slot].generation; + memset(ap_scoreboard_image->servers[child_slot], 0, sizeof(worker_score) * HARD_THREAD_LIMIT); + + /* Set up an OS/2 queue for passing connections & termination requests + * to worker threads + */ + rc = DosCreateQueue(&workq, QUE_FIFO, apr_psprintf(pchild, "/queues/httpd/work.%d", my_pid)); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00193) + "unable to create work queue, exiting"); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create initial pool of worker threads */ + for (c = 0; c < ap_min_spare_threads; c++) { +// ap_scoreboard_image->servers[child_slot][c].tid = _beginthread(worker_main, NULL, 128*1024, (void *)c); + } + + /* Start maintenance thread */ + server_maint_tid = _beginthread(server_maintenance, NULL, 32768, NULL); + + /* Set up poll */ + for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) { + num_listeners++; + } + + apr_pollset_create(&pollset, num_listeners, pchild, 0); + + for (lr = ap_listeners; lr != NULL; lr = lr->next) { + apr_pollfd_t pfd = { 0 }; + + pfd.desc_type = APR_POLL_SOCKET; + pfd.desc.s = lr->sd; + pfd.reqevents = APR_POLLIN; + pfd.client_data = lr; + apr_pollset_add(pollset, &pfd); + } + + /* Main connection accept loop */ + do { + apr_pool_t *pconn; + worker_args_t *worker_args; + int last_poll_idx = 0; + + apr_pool_create(&pconn, pchild); + apr_pool_tag(pconn, "transaction"); + worker_args = apr_palloc(pconn, sizeof(worker_args_t)); + worker_args->pconn = pconn; + + if (num_listeners == 1) { + rv = apr_socket_accept(&worker_args->conn_sd, ap_listeners->sd, pconn); + } else { + const apr_pollfd_t *poll_results; + apr_int32_t num_poll_results; + + rc = DosRequestMutexSem(ap_mpm_accept_mutex, SEM_INDEFINITE_WAIT); + + if (shutdown_pending) { + DosReleaseMutexSem(ap_mpm_accept_mutex); + break; + } + + rv = APR_FROM_OS_ERROR(rc); + + if (rv == APR_SUCCESS) { + rv = apr_pollset_poll(pollset, -1, &num_poll_results, &poll_results); + DosReleaseMutexSem(ap_mpm_accept_mutex); + } + + if (rv == APR_SUCCESS) { + if (last_poll_idx >= num_listeners) { + last_poll_idx = 0; + } + + lr = poll_results[last_poll_idx++].client_data; + rv = apr_socket_accept(&worker_args->conn_sd, lr->sd, pconn); + last_poll_idx++; + } + } + + if (rv != APR_SUCCESS) { + if (!APR_STATUS_IS_EINTR(rv)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00194) + "apr_socket_accept"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } else { + DosWriteQueue(workq, WORKTYPE_CONN, sizeof(worker_args_t), worker_args, 0); + requests_this_child++; + } + + if (ap_max_requests_per_child != 0 && requests_this_child >= ap_max_requests_per_child) + break; + } while (!shutdown_pending && ap_my_generation == ap_scoreboard_image->global->running_generation); + + ap_scoreboard_image->parent[child_slot].quiescing = 1; + DosPostEventSem(shutdown_event); + DosWaitThread(&server_maint_tid, DCWW_WAIT); + + if (is_graceful) { + char someleft; + + /* tell our worker threads to exit */ + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0); + } + } + + do { + someleft = 0; + + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + someleft = 1; + DosSleep(1000); + break; + } + } + } while (someleft); + } else { + DosPurgeQueue(workq); + + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) { + DosKillThread(ap_scoreboard_image->servers[child_slot][c].tid); + } + } + } + + apr_pool_destroy(pchild); +} + + + +void add_worker() +{ + int thread_slot; + int stacksize = ap_thread_stacksize == 0 ? 128*1024 : ap_thread_stacksize; + + /* Find a free thread slot */ + for (thread_slot=0; thread_slot < HARD_THREAD_LIMIT; thread_slot++) { + if (ap_scoreboard_image->servers[child_slot][thread_slot].status == SERVER_DEAD) { + ap_scoreboard_image->servers[child_slot][thread_slot].status = SERVER_STARTING; + ap_scoreboard_image->servers[child_slot][thread_slot].tid = + _beginthread(worker_main, NULL, stacksize, (void *)thread_slot); + break; + } + } +} + + + +ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec, + EXCEPTIONREGISTRATIONRECORD *pRegRec, + CONTEXTRECORD *pContext, + PVOID p) +{ + int c; + + if (pReportRec->fHandlerFlags & EH_NESTED_CALL) { + return XCPT_CONTINUE_SEARCH; + } + + if (pReportRec->ExceptionNum == XCPT_ACCESS_VIOLATION || + pReportRec->ExceptionNum == XCPT_INTEGER_DIVIDE_BY_ZERO) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00195) + "caught exception in worker thread, initiating child shutdown pid=%d", getpid()); + for (c=0; c<HARD_THREAD_LIMIT; c++) { + if (ap_scoreboard_image->servers[child_slot][c].tid == _gettid()) { + ap_scoreboard_image->servers[child_slot][c].status = SERVER_DEAD; + break; + } + } + + /* Shut down process ASAP, it could be quite unhealthy & leaking resources */ + shutdown_pending = 1; + ap_scoreboard_image->parent[child_slot].quiescing = 1; + kill(getpid(), SIGHUP); + DosUnwindException(UNWIND_ALL, 0, 0); + } + + return XCPT_CONTINUE_SEARCH; +} + + + +static void worker_main(void *vpArg) +{ + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; + long conn_id; + conn_rec *current_conn; + apr_pool_t *pconn; + apr_allocator_t *allocator; + apr_bucket_alloc_t *bucket_alloc; + worker_args_t *worker_args; + HQUEUE workq; + PID owner; + int rc; + REQUESTDATA rd; + ULONG len; + BYTE priority; + int thread_slot = (int)vpArg; + EXCEPTIONREGISTRATIONRECORD reg_rec = { NULL, thread_exception_handler }; + ap_sb_handle_t *sbh; + + /* Trap exceptions in this thread so we don't take down the whole process */ + DosSetExceptionHandler( ®_rec ); + + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); + + rc = DosOpenQueue(&owner, &workq, + apr_psprintf(pchild, "/queues/httpd/work.%d", getpid())); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00196) + "unable to open work queue, exiting"); + ap_scoreboard_image->servers[child_slot][thread_slot].tid = 0; + } + + conn_id = ID_FROM_CHILD_THREAD(child_slot, thread_slot); + ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_READY, + NULL); + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + bucket_alloc = apr_bucket_alloc_create_ex(allocator); + + while (rc = DosReadQueue(workq, &rd, &len, (PPVOID)&worker_args, 0, DCWW_WAIT, &priority, NULLHANDLE), + rc == 0 && rd.ulData != WORKTYPE_EXIT) { + pconn = worker_args->pconn; + ap_create_sb_handle(&sbh, pconn, child_slot, thread_slot); + current_conn = ap_run_create_connection(pconn, ap_server_conf, + worker_args->conn_sd, conn_id, + sbh, bucket_alloc); + + if (current_conn) { + current_conn->current_thread = thd; + ap_process_connection(current_conn, worker_args->conn_sd); + ap_lingering_close(current_conn); + } + + apr_pool_destroy(pconn); + ap_update_child_status_from_indexes(child_slot, thread_slot, + SERVER_READY, NULL); + } + + ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_DEAD, + NULL); + + apr_bucket_alloc_destroy(bucket_alloc); + apr_allocator_destroy(allocator); +} + + + +static void server_maintenance(void *vpArg) +{ + int num_idle, num_needed; + ULONG num_pending = 0; + int threadnum; + HQUEUE workq; + ULONG rc; + PID owner; + + rc = DosOpenQueue(&owner, &workq, + apr_psprintf(pchild, "/queues/httpd/work.%d", getpid())); + + if (rc) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00197) + "unable to open work queue in maintenance thread"); + return; + } + + do { + for (num_idle=0, threadnum=0; threadnum < HARD_THREAD_LIMIT; threadnum++) { + num_idle += ap_scoreboard_image->servers[child_slot][threadnum].status == SERVER_READY; + } + + DosQueryQueue(workq, &num_pending); + num_needed = ap_min_spare_threads - num_idle + num_pending; + + if (num_needed > 0) { + for (threadnum=0; threadnum < num_needed; threadnum++) { + add_worker(); + } + } + + if (num_idle - num_pending > ap_max_spare_threads) { + DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0); + } + } while (DosWaitEventSem(shutdown_event, 500) == ERROR_TIMEOUT); +} + + + +/* Signal handling routines */ + +static void sig_term(int sig) +{ + shutdown_pending = 1; + is_graceful = 0; + signal(SIGTERM, SIG_DFL); +} + + + +static void sig_hup(int sig) +{ + shutdown_pending = 1; + is_graceful = 1; +} + + + +static void set_signals() +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_term; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00198) "sigaction(SIGTERM)"); + + sa.sa_handler = sig_hup; + + if (sigaction(SIGHUP, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00199) "sigaction(SIGHUP)"); +} |