summaryrefslogtreecommitdiffstats
path: root/server/mpm/mpmt_os2/mpmt_os2.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/mpm/mpmt_os2/mpmt_os2.c')
-rw-r--r--server/mpm/mpmt_os2/mpmt_os2.c614
1 files changed, 614 insertions, 0 deletions
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 */
+};