1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* Multi-process, multi-threaded MPM for OS/2
20 * - a main, parent process
21 * - a small, static number of child processes
23 * The parent process's job is to manage the child processes. This involves
24 * spawning children as required to ensure there are always ap_daemons_to_start
25 * processes accepting connections.
27 * Each child process consists of a a pool of worker threads and a
28 * main thread that accepts connections & passes them to the workers via
29 * a work queue. The worker thread pool is dynamic, managed by a maintanence
30 * thread so that the number of idle threads is kept between
31 * min_spare_threads & max_spare_threads.
37 - Enforce MaxClients somehow
41 #define INCL_DOSERRORS
43 #include "ap_config.h"
45 #include "mpm_default.h"
46 #include "http_main.h"
48 #include "http_config.h"
49 #include "http_core.h" /* for get_remote_host */
50 #include "http_connection.h"
53 #include "ap_listen.h"
54 #include "apr_portable.h"
55 #include "mpm_common.h"
56 #include "apr_strings.h"
60 /* We don't need many processes,
61 * they're only for redundancy in the event of a crash
63 #define HARD_SERVER_LIMIT 10
65 /* Limit on the total number of threads per process
67 #ifndef HARD_THREAD_LIMIT
68 #define HARD_THREAD_LIMIT 256
71 server_rec *ap_server_conf;
72 static apr_pool_t *pconf = NULL; /* Pool for config stuff */
73 static const char *ap_pid_fname=NULL;
76 static int one_process = 0;
77 static int ap_daemons_to_start = 0;
78 static int ap_thread_limit = 0;
79 static int ap_max_requests_per_child = 0;
80 int ap_min_spare_threads = 0;
81 int ap_max_spare_threads = 0;
83 /* Keep track of a few interesting statistics */
84 int ap_max_daemons_limit = -1;
86 /* volatile just in case */
87 static int volatile shutdown_pending;
88 static int volatile restart_pending;
89 static int volatile is_graceful = 0;
90 ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */
91 static int is_parent_process=TRUE;
92 HMTX ap_mpm_accept_mutex = 0;
94 /* An array of these is stored in a shared memory area for passing
95 * sockets from the parent to child processes
98 struct sockaddr_in name;
99 apr_os_sock_t listen_fd;
104 listen_socket_t listeners[1];
107 static char master_main();
108 static void spawn_child(int slot);
109 void ap_mpm_child_main(apr_pool_t *pconf);
110 static void set_signals();
113 int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s )
115 char *listener_shm_name;
116 parent_info_t *parent_info;
122 DosSetMaxFH(ap_thread_limit * 2);
123 listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid());
124 rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ);
125 is_parent_process = rc != 0;
126 ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid());
131 int num_listeners = 0;
133 ap_mpm_accept_mutex = parent_info->accept_mutex;
135 /* Set up a default listener if necessary */
136 if (ap_listeners == NULL) {
137 ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec));
139 apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC,
140 DEFAULT_HTTP_PORT, 0, s->process->pool);
141 apr_socket_create(&lr->sd, lr->bind_addr->family,
142 SOCK_STREAM, 0, s->process->pool);
145 for (lr = ap_listeners; lr; lr = lr->next) {
147 apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf);
148 apr_socket_addr_get(&sa, APR_LOCAL, lr->sd);
152 DosFreeMem(parent_info);
155 ap_mpm_child_main(pconf);
163 is_parent_process = TRUE;
165 if (ap_setup_listeners(ap_server_conf) < 1) {
166 ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
167 "no listening sockets available, shutting down");
171 ap_log_pid(pconf, ap_pid_fname);
173 restart = master_main();
175 ap_scoreboard_image->global->running_generation = ap_my_generation;
178 const char *pidfile = ap_server_root_relative(pconf, ap_pid_fname);
180 if (pidfile != NULL && remove(pidfile) == 0) {
181 ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS,
182 ap_server_conf, "removed PID file %s (pid=%d)",
186 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
187 "caught SIGTERM, shutting down");
190 } /* Parent process */
192 return 0; /* Restart */
197 /* Main processing of the parent process
198 * returns TRUE if restarting
200 static char master_main()
202 server_rec *s = ap_server_conf;
204 parent_info_t *parent_info;
205 char *listener_shm_name;
206 int listener_num, num_listeners, slot;
209 printf("%s \n", ap_get_server_description());
212 if (ap_setup_listeners(ap_server_conf) < 1) {
213 ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
214 "no listening sockets available, shutting down");
218 /* Allocate a shared memory block for the array of listeners */
219 for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
223 listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid());
224 rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name,
225 sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t),
226 PAG_READ|PAG_WRITE|PAG_COMMIT);
229 ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
230 "failure allocating shared memory, shutting down");
234 /* Store the listener sockets in the shared memory area for our children to see */
235 for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) {
236 apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd);
239 /* Create mutex to prevent multiple child processes from detecting
240 * a connection with apr_poll()
243 rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE);
246 ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
247 "failure creating accept mutex, shutting down");
251 parent_info->accept_mutex = ap_mpm_accept_mutex;
253 /* Allocate shared memory for scoreboard */
254 if (ap_scoreboard_image == NULL) {
256 rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname,
257 ap_calc_scoreboard_size(),
258 PAG_COMMIT|PAG_READ|PAG_WRITE);
261 ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
262 "unable to allocate shared memory for scoreboard , exiting");
266 ap_init_scoreboard(sb_mem);
269 ap_scoreboard_image->global->restart_time = apr_time_now();
270 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
271 "%s configured -- resuming normal operations",
272 ap_get_server_description());
273 ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
274 "Server built: %s", ap_get_server_built());
275 #ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
276 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
277 "AcceptMutex: %s (default: %s)",
278 apr_proc_mutex_name(accept_mutex),
279 apr_proc_mutex_defname());
282 ap_scoreboard_image->parent[0].pid = getpid();
283 ap_mpm_child_main(pconf);
287 while (!restart_pending && !shutdown_pending) {
290 int active_children = 0;
292 /* Count number of active children */
293 for (slot=0; slot < HARD_SERVER_LIMIT; slot++) {
294 active_children += ap_scoreboard_image->parent[slot].pid != 0 &&
295 !ap_scoreboard_image->parent[slot].quiescing;
298 /* Spawn children if needed */
299 for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) {
300 if (ap_scoreboard_image->parent[slot].pid == 0) {
306 rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0);
309 /* A child has terminated, remove its scoreboard entry & terminate if necessary */
310 for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++);
312 if (slot < HARD_SERVER_LIMIT) {
313 ap_scoreboard_image->parent[slot].pid = 0;
314 ap_scoreboard_image->parent[slot].quiescing = 0;
316 if (proc_rc.codeTerminate == TC_EXIT) {
317 /* Child terminated normally, check its exit code and
318 * terminate server if child indicates a fatal error
320 if (proc_rc.codeResult == APEXIT_CHILDFATAL)
324 } else if (rc == ERROR_CHILD_NOT_COMPLETE) {
325 /* No child exited, lets sleep for a while.... */
326 apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
330 /* Signal children to shut down, either gracefully or immediately */
331 for (slot=0; slot<HARD_SERVER_LIMIT; slot++) {
332 kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM);
335 DosFreeMem(parent_info);
336 return restart_pending;
341 static void spawn_child(int slot)
345 char fail_module[100];
346 char progname[CCHMAXPATH];
350 ap_scoreboard_image->parent[slot].generation = ap_my_generation;
351 DosGetInfoBlocks(&ptib, &ppib);
352 DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname);
353 rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT,
354 ppib->pib_pchcmd, NULL, &proc_rc, progname);
357 ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
358 "error spawning child, slot %d", slot);
361 if (ap_max_daemons_limit < slot) {
362 ap_max_daemons_limit = slot;
365 ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate;
370 /* Signal handling routines */
372 static void sig_term(int sig)
374 shutdown_pending = 1;
375 signal(SIGTERM, SIG_DFL);
380 static void sig_restart(int sig)
382 if (sig == SIGUSR1) {
391 static void set_signals()
395 sigemptyset(&sa.sa_mask);
397 sa.sa_handler = sig_term;
399 if (sigaction(SIGTERM, &sa, NULL) < 0)
400 ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
402 if (sigaction(SIGINT, &sa, NULL) < 0)
403 ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)");
405 sa.sa_handler = sig_restart;
407 if (sigaction(SIGHUP, &sa, NULL) < 0)
408 ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)");
409 if (sigaction(SIGUSR1, &sa, NULL) < 0)
410 ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGUSR1)");
415 /* Enquiry functions used get MPM status info */
417 AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
419 switch (query_code) {
420 case AP_MPMQ_MAX_DAEMON_USED:
421 *result = ap_max_daemons_limit;
423 case AP_MPMQ_IS_THREADED:
424 *result = AP_MPMQ_DYNAMIC;
426 case AP_MPMQ_IS_FORKED:
427 *result = AP_MPMQ_NOT_SUPPORTED;
429 case AP_MPMQ_HARD_LIMIT_DAEMONS:
430 *result = HARD_SERVER_LIMIT;
432 case AP_MPMQ_HARD_LIMIT_THREADS:
433 *result = HARD_THREAD_LIMIT;
435 case AP_MPMQ_MIN_SPARE_DAEMONS:
438 case AP_MPMQ_MAX_SPARE_DAEMONS:
441 case AP_MPMQ_MAX_REQUESTS_DAEMON:
442 *result = ap_max_requests_per_child;
451 /* Configuration handling stuff */
453 static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
455 one_process = ap_exists_config_define("ONE_PROCESS") ||
456 ap_exists_config_define("DEBUG");
458 ap_listen_pre_config();
459 ap_daemons_to_start = DEFAULT_START_DAEMON;
460 ap_thread_limit = HARD_THREAD_LIMIT;
461 ap_pid_fname = DEFAULT_PIDLOG;
462 ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
463 ap_extended_status = 0;
464 ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD;
465 ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD;
466 #ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
467 ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
475 static int mpmt_os2_check_config(apr_pool_t *p, apr_pool_t *plog,
476 apr_pool_t *ptemp, server_rec *s)
478 static int restart_num = 0;
481 /* we want this only the first time around */
482 if (restart_num++ == 0) {
486 if (ap_daemons_to_start < 0) {
488 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,
489 "WARNING: StartServers of %d not allowed, "
490 "increasing to 1.", ap_daemons_to_start);
492 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
493 "StartServers of %d not allowed, increasing to 1",
494 ap_daemons_to_start);
496 ap_daemons_to_start = 1;
499 if (ap_min_spare_threads < 1) {
501 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,
502 "WARNING: MinSpareThreads of %d not allowed, "
503 "increasing to 1", ap_min_spare_threads);
504 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,
505 " to avoid almost certain server failure.");
506 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,
507 " Please read the documentation.");
509 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
510 "MinSpareThreads of %d not allowed, increasing to 1",
511 ap_min_spare_threads);
513 ap_min_spare_threads = 1;
521 static void mpmt_os2_hooks(apr_pool_t *p)
523 ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
524 ap_hook_check_config(mpmt_os2_check_config, NULL, NULL, APR_HOOK_MIDDLE);
529 static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)
531 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
537 ap_daemons_to_start = atoi(arg);
543 static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy,
546 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
552 ap_min_spare_threads = atoi(arg);
558 static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy,
561 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
567 ap_max_spare_threads = atoi(arg);
573 static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg)
580 static const command_rec mpmt_os2_cmds[] = {
582 AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF,
583 "Number of child processes launched at server startup" ),
584 AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF,
585 "Minimum number of idle children, to handle request spikes"),
586 AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF,
587 "Maximum number of idle children"),
588 AP_INIT_TAKE1("User", ignore_cmd, NULL, RSRC_CONF,
589 "Not applicable on this platform"),
590 AP_INIT_TAKE1("Group", ignore_cmd, NULL, RSRC_CONF,
591 "Not applicable on this platform"),
592 AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \
593 "Not applicable on this platform"),
597 module AP_MODULE_DECLARE_DATA mpm_mpmt_os2_module = {
599 NULL, /* hook to run before apache parses args */
600 NULL, /* create per-directory config structure */
601 NULL, /* merge per-directory config structures */
602 NULL, /* create per-server config structure */
603 NULL, /* merge per-server config structures */
604 mpmt_os2_cmds, /* command apr_table_t */
605 mpmt_os2_hooks, /* register_hooks */