]> granicus.if.org Git - apache/blob - server/mpm/mpmt_os2/mpmt_os2.c
32b8e1e79d8b3e79afce17ec491da79e8b8731f4
[apache] / server / mpm / mpmt_os2 / mpmt_os2.c
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /* Multi-process, multi-threaded MPM for OS/2
18  *
19  * Server consists of
20  * - a main, parent process
21  * - a small, static number of child processes
22  *
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.
26  *
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.
32  *
33  */
34
35 /*
36  Todo list
37  - Enforce MaxClients somehow
38 */
39 #define INCL_NOPMAPI
40 #define INCL_DOS
41 #define INCL_DOSERRORS
42
43 #include "ap_config.h"
44 #include "httpd.h"
45 #include "mpm_default.h"
46 #include "http_main.h"
47 #include "http_log.h"
48 #include "http_config.h"
49 #include "http_core.h"  /* for get_remote_host */
50 #include "http_connection.h"
51 #include "mpm.h"
52 #include "ap_mpm.h"
53 #include "ap_listen.h"
54 #include "apr_portable.h"
55 #include "mpm_common.h"
56 #include "apr_strings.h"
57 #include <os2.h>
58 #include <process.h>
59
60 /* We don't need many processes,
61  * they're only for redundancy in the event of a crash
62  */
63 #define HARD_SERVER_LIMIT 10
64
65 /* Limit on the total number of threads per process
66  */
67 #ifndef HARD_THREAD_LIMIT
68 #define HARD_THREAD_LIMIT 256
69 #endif
70
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;
74
75 /* Config globals */
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;
82
83 /* Keep track of a few interesting statistics */
84 int ap_max_daemons_limit = -1;
85
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;
93
94 /* An array of these is stored in a shared memory area for passing
95  * sockets from the parent to child processes
96  */
97 typedef struct {
98     struct sockaddr_in name;
99     apr_os_sock_t listen_fd;
100 } listen_socket_t;
101
102 typedef struct {
103     HMTX accept_mutex;
104     listen_socket_t listeners[1];
105 } parent_info_t;
106
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();
111
112
113 int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s )
114 {
115     char *listener_shm_name;
116     parent_info_t *parent_info;
117     ULONG rc;
118     pconf = _pconf;
119     ap_server_conf = s;
120     restart_pending = 0;
121
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());
127
128     if (rc == 0) {
129         /* Child process */
130         ap_listen_rec *lr;
131         int num_listeners = 0;
132
133         ap_mpm_accept_mutex = parent_info->accept_mutex;
134
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));
138             ap_listeners = lr;
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);
143         }
144
145         for (lr = ap_listeners; lr; lr = lr->next) {
146             apr_sockaddr_t *sa;
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);
149             num_listeners++;
150         }
151
152         DosFreeMem(parent_info);
153
154         /* Do the work */
155         ap_mpm_child_main(pconf);
156
157         /* Outta here */
158         return 1;
159     }
160     else {
161         /* Parent process */
162         char restart;
163         is_parent_process = TRUE;
164
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");
168             return 1;
169         }
170
171         ap_log_pid(pconf, ap_pid_fname);
172
173         restart = master_main();
174         ++ap_my_generation;
175         ap_scoreboard_image->global->running_generation = ap_my_generation;
176
177         if (!restart) {
178             const char *pidfile = ap_server_root_relative(pconf, ap_pid_fname);
179
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)",
183                              pidfile, getpid());
184             }
185
186             ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
187                          "caught SIGTERM, shutting down");
188             return 1;
189         }
190     }  /* Parent process */
191
192     return 0; /* Restart */
193 }
194
195
196
197 /* Main processing of the parent process
198  * returns TRUE if restarting
199  */
200 static char master_main()
201 {
202     server_rec *s = ap_server_conf;
203     ap_listen_rec *lr;
204     parent_info_t *parent_info;
205     char *listener_shm_name;
206     int listener_num, num_listeners, slot;
207     ULONG rc;
208
209     printf("%s \n", ap_get_server_description());
210     set_signals();
211
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");
215         return FALSE;
216     }
217
218     /* Allocate a shared memory block for the array of listeners */
219     for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
220         num_listeners++;
221     }
222
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);
227
228     if (rc) {
229         ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
230                      "failure allocating shared memory, shutting down");
231         return FALSE;
232     }
233
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);
237     }
238
239     /* Create mutex to prevent multiple child processes from detecting
240      * a connection with apr_poll()
241      */
242
243     rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE);
244
245     if (rc) {
246         ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
247                      "failure creating accept mutex, shutting down");
248         return FALSE;
249     }
250
251     parent_info->accept_mutex = ap_mpm_accept_mutex;
252
253     /* Allocate shared memory for scoreboard */
254     if (ap_scoreboard_image == NULL) {
255         void *sb_mem;
256         rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname,
257                                ap_calc_scoreboard_size(),
258                                PAG_COMMIT|PAG_READ|PAG_WRITE);
259
260         if (rc) {
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");
263             return FALSE;
264         }
265
266         ap_init_scoreboard(sb_mem);
267     }
268
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());
280 #endif
281     if (one_process) {
282         ap_scoreboard_image->parent[0].pid = getpid();
283         ap_mpm_child_main(pconf);
284         return FALSE;
285     }
286
287     while (!restart_pending && !shutdown_pending) {
288         RESULTCODES proc_rc;
289         PID child_pid;
290         int active_children = 0;
291
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;
296         }
297
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) {
301                 spawn_child(slot);
302                 active_children++;
303             }
304         }
305
306         rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0);
307
308         if (rc == 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++);
311
312             if (slot < HARD_SERVER_LIMIT) {
313                 ap_scoreboard_image->parent[slot].pid = 0;
314                 ap_scoreboard_image->parent[slot].quiescing = 0;
315
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
319                      */
320                     if (proc_rc.codeResult == APEXIT_CHILDFATAL)
321                         break;
322                 }
323             }
324         } else if (rc == ERROR_CHILD_NOT_COMPLETE) {
325             /* No child exited, lets sleep for a while.... */
326             apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
327         }
328     }
329
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);
333     }
334
335     DosFreeMem(parent_info);
336     return restart_pending;
337 }
338
339
340
341 static void spawn_child(int slot)
342 {
343     PPIB ppib;
344     PTIB ptib;
345     char fail_module[100];
346     char progname[CCHMAXPATH];
347     RESULTCODES proc_rc;
348     ULONG rc;
349
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);
355
356     if (rc) {
357         ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
358                      "error spawning child, slot %d", slot);
359     }
360
361     if (ap_max_daemons_limit < slot) {
362         ap_max_daemons_limit = slot;
363     }
364
365     ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate;
366 }
367
368
369
370 /* Signal handling routines */
371
372 static void sig_term(int sig)
373 {
374     shutdown_pending = 1;
375     signal(SIGTERM, SIG_DFL);
376 }
377
378
379
380 static void sig_restart(int sig)
381 {
382     if (sig == SIGUSR1) {
383         is_graceful = 1;
384     }
385
386     restart_pending = 1;
387 }
388
389
390
391 static void set_signals()
392 {
393     struct sigaction sa;
394
395     sigemptyset(&sa.sa_mask);
396     sa.sa_flags = 0;
397     sa.sa_handler = sig_term;
398
399     if (sigaction(SIGTERM, &sa, NULL) < 0)
400         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
401
402     if (sigaction(SIGINT, &sa, NULL) < 0)
403         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)");
404
405     sa.sa_handler = sig_restart;
406
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)");
411 }
412
413
414
415 /* Enquiry functions used get MPM status info */
416
417 AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
418 {
419     switch (query_code) {
420         case AP_MPMQ_MAX_DAEMON_USED:
421             *result = ap_max_daemons_limit;
422             return APR_SUCCESS;
423         case AP_MPMQ_IS_THREADED:
424             *result = AP_MPMQ_DYNAMIC;
425             return APR_SUCCESS;
426         case AP_MPMQ_IS_FORKED:
427             *result = AP_MPMQ_NOT_SUPPORTED;
428             return APR_SUCCESS;
429         case AP_MPMQ_HARD_LIMIT_DAEMONS:
430             *result = HARD_SERVER_LIMIT;
431             return APR_SUCCESS;
432         case AP_MPMQ_HARD_LIMIT_THREADS:
433             *result = HARD_THREAD_LIMIT;
434             return APR_SUCCESS;
435         case AP_MPMQ_MIN_SPARE_DAEMONS:
436             *result = 0;
437             return APR_SUCCESS;
438         case AP_MPMQ_MAX_SPARE_DAEMONS:
439             *result = 0;
440             return APR_SUCCESS;
441         case AP_MPMQ_MAX_REQUESTS_DAEMON:
442             *result = ap_max_requests_per_child;
443             return APR_SUCCESS;
444     }
445     return APR_ENOTIMPL;
446 }
447
448
449
450
451 /* Configuration handling stuff */
452
453 static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
454 {
455     one_process = ap_exists_config_define("ONE_PROCESS") ||
456                   ap_exists_config_define("DEBUG");
457     is_graceful = 0;
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;
468 #endif
469
470     return OK;
471 }
472
473
474
475 static int mpmt_os2_check_config(apr_pool_t *p, apr_pool_t *plog,
476                                  apr_pool_t *ptemp, server_rec *s)
477 {
478     static int restart_num = 0;
479     int startup = 0;
480
481     /* we want this only the first time around */
482     if (restart_num++ == 0) {
483         startup = 1;
484     }
485
486     if (ap_daemons_to_start < 0) {
487         if (startup) {
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);
491         } else {
492             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
493                          "StartServers of %d not allowed, increasing to 1",
494                          ap_daemons_to_start);
495         }
496         ap_daemons_to_start = 1;
497     }
498
499     if (ap_min_spare_threads < 1) {
500         if (startup) {
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.");
508         } else {
509             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
510                          "MinSpareThreads of %d not allowed, increasing to 1",
511                          ap_min_spare_threads);
512         }
513         ap_min_spare_threads = 1;
514     }
515
516     return OK;
517 }
518
519
520
521 static void mpmt_os2_hooks(apr_pool_t *p)
522 {
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);
525 }
526
527
528
529 static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)
530 {
531     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
532
533     if (err != NULL) {
534         return err;
535     }
536
537     ap_daemons_to_start = atoi(arg);
538     return NULL;
539 }
540
541
542
543 static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy,
544                                          const char *arg)
545 {
546     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
547
548     if (err != NULL) {
549         return err;
550     }
551
552     ap_min_spare_threads = atoi(arg);
553     return NULL;
554 }
555
556
557
558 static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy,
559                                          const char *arg)
560 {
561     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
562
563     if (err != NULL) {
564         return err;
565     }
566
567     ap_max_spare_threads = atoi(arg);
568     return NULL;
569 }
570
571
572
573 static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg)
574 {
575     return NULL;
576 }
577
578
579
580 static const command_rec mpmt_os2_cmds[] = {
581 LISTEN_COMMANDS,
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"),
594 { NULL }
595 };
596
597 module AP_MODULE_DECLARE_DATA mpm_mpmt_os2_module = {
598     MPM20_MODULE_STUFF,
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 */
606 };