]> granicus.if.org Git - apache/blob - server/mpm/mpmt_os2/mpmt_os2.c
switch to APR 1.0 API (which is still in flux)
[apache] / server / mpm / mpmt_os2 / mpmt_os2.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 /* Multi-process, multi-threaded MPM for OS/2
60  *
61  * Server consists of
62  * - a main, parent process
63  * - a small, static number of child processes
64  *
65  * The parent process's job is to manage the child processes. This involves
66  * spawning children as required to ensure there are always ap_daemons_to_start
67  * processes accepting connections.
68  *
69  * Each child process consists of a a pool of worker threads and a
70  * main thread that accepts connections & passes them to the workers via
71  * a work queue. The worker thread pool is dynamic, managed by a maintanence
72  * thread so that the number of idle threads is kept between
73  * min_spare_threads & max_spare_threads.
74  *
75  */
76
77 /*
78  Todo list
79  - Enforce MaxClients somehow
80 */
81 #define CORE_PRIVATE
82 #define INCL_NOPMAPI
83 #define INCL_DOS
84 #define INCL_DOSERRORS
85
86 #include "ap_config.h"
87 #include "httpd.h"
88 #include "mpm_default.h"
89 #include "http_main.h"
90 #include "http_log.h"
91 #include "http_config.h"
92 #include "http_core.h"          /* for get_remote_host */
93 #include "http_connection.h"
94 #include "mpm.h"
95 #include "ap_mpm.h"
96 #include "ap_listen.h"
97 #include "apr_portable.h"
98 #include "mpm_common.h"
99 #include "apr_strings.h"
100 #include <os2.h>
101 #include <process.h>
102
103 /* We don't need many processes, 
104  * they're only for redundancy in the event of a crash 
105  */
106 #define HARD_SERVER_LIMIT 10
107
108 /* Limit on the total number of threads per process
109  */
110 #ifndef HARD_THREAD_LIMIT
111 #define HARD_THREAD_LIMIT 256
112 #endif
113
114 server_rec *ap_server_conf;
115 static apr_pool_t *pconf = NULL;                /* Pool for config stuff */
116 static const char *ap_pid_fname=NULL;
117
118 /* Config globals */
119 static int one_process = 0;
120 static int ap_daemons_to_start = 0;
121 static int ap_thread_limit = 0;
122 static int ap_max_requests_per_child = 0;
123 int ap_min_spare_threads = 0;
124 int ap_max_spare_threads = 0;
125
126 /* Keep track of a few interesting statistics */
127 int ap_max_daemons_limit = -1;
128
129 /* volatile just in case */
130 static int volatile shutdown_pending;
131 static int volatile restart_pending;
132 static int volatile is_graceful = 0;
133 ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */
134 static int is_parent_process=TRUE;
135 HMTX ap_mpm_accept_mutex = 0;
136
137 /* An array of these is stored in a shared memory area for passing
138  * sockets from the parent to child processes
139  */
140 typedef struct {
141     struct sockaddr_in name;
142     apr_os_sock_t listen_fd;
143 } listen_socket_t;
144
145 typedef struct {
146     HMTX accept_mutex;
147     listen_socket_t listeners[1];
148 } parent_info_t;
149
150 static char master_main();
151 static void spawn_child(int slot);
152 void ap_mpm_child_main(apr_pool_t *pconf);
153 static void set_signals();
154
155
156 int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s )
157 {
158     char *listener_shm_name;
159     parent_info_t *parent_info;
160     ULONG rc;
161     pconf = _pconf;
162     ap_server_conf = s;
163     restart_pending = 0;
164
165     DosSetMaxFH(ap_thread_limit * 2);
166     listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid());
167     rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ);
168     is_parent_process = rc != 0;
169     ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid());
170
171     if (rc == 0) {
172         /* Child process */
173         ap_listen_rec *lr;
174         int num_listeners = 0;
175
176         ap_mpm_accept_mutex = parent_info->accept_mutex;
177
178         /* Set up a default listener if necessary */
179         if (ap_listeners == NULL) {
180             ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec));
181             ap_listeners = lr;
182             apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC,
183                                   DEFAULT_HTTP_PORT, 0, s->process->pool);
184             apr_socket_create(&lr->sd, lr->bind_addr->family,
185                               SOCK_STREAM, 0, s->process->pool);
186         }
187
188         for (lr = ap_listeners; lr; lr = lr->next) {
189             apr_sockaddr_t *sa;
190             apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf);
191             apr_socket_addr_get(&sa, APR_LOCAL, lr->sd);
192             num_listeners++;
193         }
194
195         DosFreeMem(parent_info);
196
197         /* Do the work */
198         ap_mpm_child_main(pconf);
199
200         /* Outta here */
201         return 1;
202     }
203     else {
204         /* Parent process */
205         char restart;
206         is_parent_process = TRUE;
207
208         if (ap_setup_listeners(ap_server_conf) < 1) {
209             ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
210                          "no listening sockets available, shutting down");
211             return 1;
212         }
213
214         ap_log_pid(pconf, ap_pid_fname);
215
216         restart = master_main();
217         ++ap_my_generation;
218         ap_scoreboard_image->global->running_generation = ap_my_generation;
219
220         if (!restart) {
221             const char *pidfile = ap_server_root_relative(pconf, ap_pid_fname);
222
223             if (pidfile != NULL && remove(pidfile) == 0) {
224                 ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS,
225                              ap_server_conf, "removed PID file %s (pid=%d)",
226                              pidfile, getpid());
227             }
228
229             ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
230                          "caught SIGTERM, shutting down");
231             return 1;
232         }
233     }  /* Parent process */
234
235     return 0; /* Restart */
236 }
237
238
239
240 /* Main processing of the parent process
241  * returns TRUE if restarting
242  */
243 static char master_main()
244 {
245     server_rec *s = ap_server_conf;
246     ap_listen_rec *lr;
247     parent_info_t *parent_info;
248     char *listener_shm_name;
249     int listener_num, num_listeners, slot;
250     ULONG rc;
251
252     printf("%s \n", ap_get_server_version());
253     set_signals();
254
255     if (ap_setup_listeners(ap_server_conf) < 1) {
256         ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
257                      "no listening sockets available, shutting down");
258         return FALSE;
259     }
260
261     /* Allocate a shared memory block for the array of listeners */
262     for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
263         num_listeners++;
264     }
265
266     listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid());
267     rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name,
268                            sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t),
269                            PAG_READ|PAG_WRITE|PAG_COMMIT);
270
271     if (rc) {
272         ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
273                      "failure allocating shared memory, shutting down");
274         return FALSE;
275     }
276
277     /* Store the listener sockets in the shared memory area for our children to see */
278     for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) {
279         apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd);
280     }
281
282     /* Create mutex to prevent multiple child processes from detecting
283      * a connection with apr_poll()
284      */
285
286     rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE);
287
288     if (rc) {
289         ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
290                      "failure creating accept mutex, shutting down");
291         return FALSE;
292     }
293
294     parent_info->accept_mutex = ap_mpm_accept_mutex;
295
296     /* Allocate shared memory for scoreboard */
297     if (ap_scoreboard_image == NULL) {
298         void *sb_mem;
299         rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname,
300                                ap_calc_scoreboard_size(),
301                                PAG_COMMIT|PAG_READ|PAG_WRITE);
302
303         if (rc) {
304             ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
305                          "unable to allocate shared memory for scoreboard , exiting");
306             return FALSE;
307         }
308
309         ap_init_scoreboard(sb_mem);
310     }
311
312     ap_scoreboard_image->global->restart_time = apr_time_now();
313     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
314                 "%s configured -- resuming normal operations",
315                 ap_get_server_version());
316     ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
317                 "Server built: %s", ap_get_server_built());
318 #ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
319     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
320                 "AcceptMutex: %s (default: %s)",
321                 apr_proc_mutex_name(accept_mutex),
322                 apr_proc_mutex_defname());
323 #endif
324     if (one_process) {
325         ap_scoreboard_image->parent[0].pid = getpid();
326         ap_mpm_child_main(pconf);
327         return FALSE;
328     }
329
330     while (!restart_pending && !shutdown_pending) {
331         RESULTCODES proc_rc;
332         PID child_pid;
333         int active_children = 0;
334
335         /* Count number of active children */
336         for (slot=0; slot < HARD_SERVER_LIMIT; slot++) {
337             active_children += ap_scoreboard_image->parent[slot].pid != 0 &&
338                 !ap_scoreboard_image->parent[slot].quiescing;
339         }
340
341         /* Spawn children if needed */
342         for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) {
343             if (ap_scoreboard_image->parent[slot].pid == 0) {
344                 spawn_child(slot);
345                 active_children++;
346             }
347         }
348
349         rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0);
350
351         if (rc == 0) {
352             /* A child has terminated, remove its scoreboard entry & terminate if necessary */
353             for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++);
354
355             if (slot < HARD_SERVER_LIMIT) {
356                 ap_scoreboard_image->parent[slot].pid = 0;
357                 ap_scoreboard_image->parent[slot].quiescing = 0;
358
359                 if (proc_rc.codeTerminate == TC_EXIT) {
360                     /* Child terminated normally, check its exit code and
361                      * terminate server if child indicates a fatal error
362                      */
363                     if (proc_rc.codeResult == APEXIT_CHILDFATAL)
364                         break;
365                 }
366             }
367         } else if (rc == ERROR_CHILD_NOT_COMPLETE) {
368             /* No child exited, lets sleep for a while.... */
369             apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
370         }
371     }
372
373     /* Signal children to shut down, either gracefully or immediately */
374     for (slot=0; slot<HARD_SERVER_LIMIT; slot++) {
375       kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM);
376     }
377
378     DosFreeMem(parent_info);
379     return restart_pending;
380 }
381
382
383
384 static void spawn_child(int slot)
385 {
386     PPIB ppib;
387     PTIB ptib;
388     char fail_module[100];
389     char progname[CCHMAXPATH];
390     RESULTCODES proc_rc;
391     ULONG rc;
392
393     ap_scoreboard_image->parent[slot].generation = ap_my_generation;
394     DosGetInfoBlocks(&ptib, &ppib);
395     DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname);
396     rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT,
397                     ppib->pib_pchcmd, NULL, &proc_rc, progname);
398
399     if (rc) {
400         ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
401                      "error spawning child, slot %d", slot);
402     }
403
404     if (ap_max_daemons_limit < slot) {
405         ap_max_daemons_limit = slot;
406     }
407
408     ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate;
409 }
410
411
412
413 /* Signal handling routines */
414
415 static void sig_term(int sig)
416 {
417     shutdown_pending = 1;
418     signal(SIGTERM, SIG_DFL);
419 }
420
421
422
423 static void sig_restart(int sig)
424 {
425     if (sig == SIGUSR1) {
426         is_graceful = 1;
427     }
428
429     restart_pending = 1;
430 }
431
432
433
434 static void set_signals()
435 {
436     struct sigaction sa;
437
438     sigemptyset(&sa.sa_mask);
439     sa.sa_flags = 0;
440     sa.sa_handler = sig_term;
441
442     if (sigaction(SIGTERM, &sa, NULL) < 0)
443         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
444
445     if (sigaction(SIGINT, &sa, NULL) < 0)
446         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)");
447
448     sa.sa_handler = sig_restart;
449
450     if (sigaction(SIGHUP, &sa, NULL) < 0)
451         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)");
452     if (sigaction(SIGUSR1, &sa, NULL) < 0)
453         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGUSR1)");
454 }
455
456
457
458 /* Enquiry functions used get MPM status info */
459
460 AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
461 {
462     switch (query_code) {
463         case AP_MPMQ_MAX_DAEMON_USED:
464             *result = ap_max_daemons_limit;
465             return APR_SUCCESS;
466         case AP_MPMQ_IS_THREADED:
467             *result = AP_MPMQ_DYNAMIC;
468             return APR_SUCCESS;
469         case AP_MPMQ_IS_FORKED:
470             *result = AP_MPMQ_NOT_SUPPORTED;
471             return APR_SUCCESS;
472         case AP_MPMQ_HARD_LIMIT_DAEMONS:
473             *result = HARD_SERVER_LIMIT;
474             return APR_SUCCESS;
475         case AP_MPMQ_HARD_LIMIT_THREADS:
476             *result = HARD_THREAD_LIMIT;
477             return APR_SUCCESS;
478         case AP_MPMQ_MIN_SPARE_DAEMONS:
479             *result = 0;
480             return APR_SUCCESS;
481         case AP_MPMQ_MAX_SPARE_DAEMONS:
482             *result = 0;
483             return APR_SUCCESS;
484         case AP_MPMQ_MAX_REQUESTS_DAEMON:
485             *result = ap_max_requests_per_child;
486             return APR_SUCCESS; 
487     }
488     return APR_ENOTIMPL;
489
490
491
492
493 int ap_graceful_stop_signalled(void)
494 {
495     return is_graceful;
496 }
497
498
499
500 /* Configuration handling stuff */
501
502 static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
503 {
504     one_process = ap_exists_config_define("ONE_PROCESS") || 
505                   ap_exists_config_define("DEBUG");
506     is_graceful = 0;
507     ap_listen_pre_config();
508     ap_daemons_to_start = DEFAULT_START_DAEMON;
509     ap_thread_limit = HARD_THREAD_LIMIT;
510     ap_pid_fname = DEFAULT_PIDLOG;
511     ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
512     ap_extended_status = 0;
513     ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD;
514     ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD;
515 #ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
516         ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
517 #endif
518
519     return OK;
520 }
521
522
523
524 static void mpmt_os2_hooks(apr_pool_t *p)
525 {
526     ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
527 }
528
529
530
531 static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)
532 {
533     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
534
535     if (err != NULL) {
536         return err;
537     }
538
539     ap_daemons_to_start = atoi(arg);
540     return NULL;
541 }
542
543
544
545 static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy,
546                                          const char *arg)
547 {
548     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
549
550     if (err != NULL) {
551         return err;
552     }
553
554     ap_min_spare_threads = atoi(arg);
555
556     if (ap_min_spare_threads <= 0) {
557        ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, 
558                     "WARNING: detected MinSpareThreads set to non-positive.");
559        ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, 
560                     "Resetting to 1 to avoid almost certain Apache failure.");
561        ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, 
562                     "Please read the documentation.");
563        ap_min_spare_threads = 1;
564     }
565        
566     return NULL;
567 }
568
569
570
571 static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy,
572                                          const char *arg)
573 {
574     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
575
576     if (err != NULL) {
577         return err;
578     }
579
580     ap_max_spare_threads = atoi(arg);
581     return NULL;
582 }
583
584
585
586 static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg)
587 {
588     return NULL;
589 }
590
591
592
593 static const command_rec mpmt_os2_cmds[] = {
594 LISTEN_COMMANDS,
595 AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF, 
596   "Number of child processes launched at server startup" ),
597 AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF,
598   "Minimum number of idle children, to handle request spikes"),
599 AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF,
600   "Maximum number of idle children"),
601 AP_INIT_TAKE1("User", ignore_cmd, NULL, RSRC_CONF,
602   "Not applicable on this platform"),
603 AP_INIT_TAKE1("Group", ignore_cmd, NULL, RSRC_CONF,
604   "Not applicable on this platform"),
605 AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \
606   "Not applicable on this platform"),
607 { NULL }
608 };
609
610 module AP_MODULE_DECLARE_DATA mpm_mpmt_os2_module = {
611     MPM20_MODULE_STUFF,
612     NULL,                       /* hook to run before apache parses args */
613     NULL,                       /* create per-directory config structure */
614     NULL,                       /* merge per-directory config structures */
615     NULL,                       /* create per-server config structure */
616     NULL,                       /* merge per-server config structures */
617     mpmt_os2_cmds,              /* command apr_table_t */
618     mpmt_os2_hooks,             /* register_hooks */
619 };