]> granicus.if.org Git - apache/blob - server/mpm/mpmt_os2/mpmt_os2.c
Backport r1690122.
[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 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 MaxRequestWorkers 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 "ap_mpm.h"
52 #include "ap_listen.h"
53 #include "apr_portable.h"
54 #include "mpm_common.h"
55 #include "scoreboard.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
74 /* Config globals */
75 static int one_process = 0;
76 static int ap_daemons_to_start = 0;
77 static int ap_thread_limit = 0;
78 int ap_min_spare_threads = 0;
79 int ap_max_spare_threads = 0;
80
81 /* Keep track of a few interesting statistics */
82 int ap_max_daemons_limit = -1;
83
84 /* volatile just in case */
85 static int volatile shutdown_pending;
86 static int volatile restart_pending;
87 static int volatile is_graceful = 0;
88 ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */
89 static int is_parent_process=TRUE;
90 HMTX ap_mpm_accept_mutex = 0;
91
92 /* An array of these is stored in a shared memory area for passing
93  * sockets from the parent to child processes
94  */
95 typedef struct {
96     struct sockaddr_in name;
97     apr_os_sock_t listen_fd;
98 } listen_socket_t;
99
100 typedef struct {
101     HMTX accept_mutex;
102     listen_socket_t listeners[1];
103 } parent_info_t;
104
105 static char master_main();
106 static void spawn_child(int slot);
107 void ap_mpm_child_main(apr_pool_t *pconf);
108 static void set_signals();
109
110
111 static int mpmt_os2_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s )
112 {
113     char *listener_shm_name;
114     parent_info_t *parent_info;
115     ULONG rc;
116     pconf = _pconf;
117     ap_server_conf = s;
118     restart_pending = 0;
119
120     DosSetMaxFH(ap_thread_limit * 2);
121     listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid());
122     rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ);
123     is_parent_process = rc != 0;
124     ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid());
125
126     if (rc == 0) {
127         /* Child process */
128         ap_listen_rec *lr;
129         int num_listeners = 0;
130
131         ap_mpm_accept_mutex = parent_info->accept_mutex;
132
133         /* Set up a default listener if necessary */
134         if (ap_listeners == NULL) {
135             ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec));
136             ap_listeners = lr;
137             apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC,
138                                   DEFAULT_HTTP_PORT, 0, s->process->pool);
139             apr_socket_create(&lr->sd, lr->bind_addr->family,
140                               SOCK_STREAM, 0, s->process->pool);
141         }
142
143         for (lr = ap_listeners; lr; lr = lr->next) {
144             apr_sockaddr_t *sa;
145             apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf);
146             apr_socket_addr_get(&sa, APR_LOCAL, lr->sd);
147             num_listeners++;
148         }
149
150         DosFreeMem(parent_info);
151
152         /* Do the work */
153         ap_mpm_child_main(pconf);
154
155         /* Outta here */
156         return 1;
157     }
158     else {
159         /* Parent process */
160         char restart;
161         is_parent_process = TRUE;
162
163         if (ap_setup_listeners(ap_server_conf) < 1) {
164             ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00200)
165                          "no listening sockets available, shutting down");
166             return 1;
167         }
168
169         ap_log_pid(pconf, ap_pid_fname);
170
171         restart = master_main();
172         ++ap_my_generation;
173         ap_scoreboard_image->global->running_generation = ap_my_generation;
174
175         if (!restart) {
176             ap_remove_pid(pconf, ap_pid_fname);
177             ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00201)
178                          "caught SIGTERM, shutting down");
179             return 1;
180         }
181     }  /* Parent process */
182
183     return 0; /* Restart */
184 }
185
186
187
188 /* Main processing of the parent process
189  * returns TRUE if restarting
190  */
191 static char master_main()
192 {
193     server_rec *s = ap_server_conf;
194     ap_listen_rec *lr;
195     parent_info_t *parent_info;
196     char *listener_shm_name;
197     int listener_num, num_listeners, slot;
198     ULONG rc;
199
200     printf("%s \n", ap_get_server_description());
201     set_signals();
202
203     if (ap_setup_listeners(ap_server_conf) < 1) {
204         ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00202)
205                      "no listening sockets available, shutting down");
206         return FALSE;
207     }
208
209     /* Allocate a shared memory block for the array of listeners */
210     for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
211         num_listeners++;
212     }
213
214     listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid());
215     rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name,
216                            sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t),
217                            PAG_READ|PAG_WRITE|PAG_COMMIT);
218
219     if (rc) {
220         ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00203)
221                      "failure allocating shared memory, shutting down");
222         return FALSE;
223     }
224
225     /* Store the listener sockets in the shared memory area for our children to see */
226     for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) {
227         apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd);
228     }
229
230     /* Create mutex to prevent multiple child processes from detecting
231      * a connection with apr_poll()
232      */
233
234     rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE);
235
236     if (rc) {
237         ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00204)
238                      "failure creating accept mutex, shutting down");
239         return FALSE;
240     }
241
242     parent_info->accept_mutex = ap_mpm_accept_mutex;
243
244     /* Allocate shared memory for scoreboard */
245     if (ap_scoreboard_image == NULL) {
246         void *sb_mem;
247         rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname,
248                                ap_calc_scoreboard_size(),
249                                PAG_COMMIT|PAG_READ|PAG_WRITE);
250
251         if (rc) {
252             ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00205)
253                          "unable to allocate shared memory for scoreboard , exiting");
254             return FALSE;
255         }
256
257         ap_init_scoreboard(sb_mem);
258     }
259
260     ap_scoreboard_image->global->restart_time = apr_time_now();
261     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00206)
262                 "%s configured -- resuming normal operations",
263                 ap_get_server_description());
264     ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00207)
265                 "Server built: %s", ap_get_server_built());
266     if (one_process) {
267         ap_scoreboard_image->parent[0].pid = getpid();
268         ap_mpm_child_main(pconf);
269         return FALSE;
270     }
271
272     while (!restart_pending && !shutdown_pending) {
273         RESULTCODES proc_rc;
274         PID child_pid;
275         int active_children = 0;
276
277         /* Count number of active children */
278         for (slot=0; slot < HARD_SERVER_LIMIT; slot++) {
279             active_children += ap_scoreboard_image->parent[slot].pid != 0 &&
280                 !ap_scoreboard_image->parent[slot].quiescing;
281         }
282
283         /* Spawn children if needed */
284         for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) {
285             if (ap_scoreboard_image->parent[slot].pid == 0) {
286                 spawn_child(slot);
287                 active_children++;
288             }
289         }
290
291         rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0);
292
293         if (rc == 0) {
294             /* A child has terminated, remove its scoreboard entry & terminate if necessary */
295             for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++);
296
297             if (slot < HARD_SERVER_LIMIT) {
298                 ap_scoreboard_image->parent[slot].pid = 0;
299                 ap_scoreboard_image->parent[slot].quiescing = 0;
300
301                 if (proc_rc.codeTerminate == TC_EXIT) {
302                     /* Child terminated normally, check its exit code and
303                      * terminate server if child indicates a fatal error
304                      */
305                     if (proc_rc.codeResult == APEXIT_CHILDFATAL)
306                         break;
307                 }
308             }
309         } else if (rc == ERROR_CHILD_NOT_COMPLETE) {
310             /* No child exited, lets sleep for a while.... */
311             apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
312         }
313     }
314
315     /* Signal children to shut down, either gracefully or immediately */
316     for (slot=0; slot<HARD_SERVER_LIMIT; slot++) {
317       kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM);
318     }
319
320     DosFreeMem(parent_info);
321     return restart_pending;
322 }
323
324
325
326 static void spawn_child(int slot)
327 {
328     PPIB ppib;
329     PTIB ptib;
330     char fail_module[100];
331     char progname[CCHMAXPATH];
332     RESULTCODES proc_rc;
333     ULONG rc;
334
335     ap_scoreboard_image->parent[slot].generation = ap_my_generation;
336     DosGetInfoBlocks(&ptib, &ppib);
337     DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname);
338     rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT,
339                     ppib->pib_pchcmd, NULL, &proc_rc, progname);
340
341     if (rc) {
342         ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00208)
343                      "error spawning child, slot %d", slot);
344     }
345
346     if (ap_max_daemons_limit < slot) {
347         ap_max_daemons_limit = slot;
348     }
349
350     ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate;
351 }
352
353
354
355 /* Signal handling routines */
356
357 static void sig_term(int sig)
358 {
359     shutdown_pending = 1;
360     signal(SIGTERM, SIG_DFL);
361 }
362
363
364
365 static void sig_restart(int sig)
366 {
367     if (sig == SIGUSR1) {
368         is_graceful = 1;
369     }
370
371     restart_pending = 1;
372 }
373
374
375
376 static void set_signals()
377 {
378     struct sigaction sa;
379
380     sigemptyset(&sa.sa_mask);
381     sa.sa_flags = 0;
382     sa.sa_handler = sig_term;
383
384     if (sigaction(SIGTERM, &sa, NULL) < 0)
385         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00209) "sigaction(SIGTERM)");
386
387     if (sigaction(SIGINT, &sa, NULL) < 0)
388         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00210) "sigaction(SIGINT)");
389
390     sa.sa_handler = sig_restart;
391
392     if (sigaction(SIGHUP, &sa, NULL) < 0)
393         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00211) "sigaction(SIGHUP)");
394     if (sigaction(SIGUSR1, &sa, NULL) < 0)
395         ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00212) "sigaction(SIGUSR1)");
396 }
397
398
399
400 /* Enquiry functions used get MPM status info */
401
402 static apr_status_t mpmt_os2_query(int query_code, int *result, apr_status_t *rv)
403 {
404     *rv = APR_SUCCESS;
405
406     switch (query_code) {
407         case AP_MPMQ_MAX_DAEMON_USED:
408             *result = ap_max_daemons_limit;
409             break;
410
411         case AP_MPMQ_IS_THREADED:
412             *result = AP_MPMQ_DYNAMIC;
413             break;
414
415         case AP_MPMQ_IS_FORKED:
416             *result = AP_MPMQ_NOT_SUPPORTED;
417             break;
418
419         case AP_MPMQ_HARD_LIMIT_DAEMONS:
420             *result = HARD_SERVER_LIMIT;
421             break;
422
423         case AP_MPMQ_HARD_LIMIT_THREADS:
424             *result = HARD_THREAD_LIMIT;
425             break;
426
427         case AP_MPMQ_MIN_SPARE_DAEMONS:
428             *result = 0;
429             break;
430
431         case AP_MPMQ_MAX_SPARE_DAEMONS:
432             *result = 0;
433             break;
434
435         case AP_MPMQ_MAX_REQUESTS_DAEMON:
436             *result = ap_max_requests_per_child;
437             break;
438
439         case AP_MPMQ_GENERATION:
440             *result = ap_my_generation;
441             break;
442
443         default:
444             *rv = APR_ENOTIMPL;
445             break;
446     }
447
448     return OK;
449 }
450
451
452
453
454 static const char *mpmt_os2_get_name(void)
455 {
456     return "mpmt_os2";
457 }
458
459
460
461
462 /* Configuration handling stuff */
463
464 static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
465 {
466     one_process = ap_exists_config_define("ONE_PROCESS") ||
467                   ap_exists_config_define("DEBUG");
468     is_graceful = 0;
469     ap_listen_pre_config();
470     ap_daemons_to_start = DEFAULT_START_DAEMON;
471     ap_thread_limit = HARD_THREAD_LIMIT;
472     ap_extended_status = 0;
473     ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD;
474     ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD;
475     ap_sys_privileges_handlers(1);
476
477     return OK;
478 }
479
480
481
482 static int mpmt_os2_check_config(apr_pool_t *p, apr_pool_t *plog,
483                                  apr_pool_t *ptemp, server_rec *s)
484 {
485     static int restart_num = 0;
486     int startup = 0;
487
488     /* we want this only the first time around */
489     if (restart_num++ == 0) {
490         startup = 1;
491     }
492
493     if (ap_daemons_to_start < 0) {
494         if (startup) {
495             ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00213)
496                          "WARNING: StartServers of %d not allowed, "
497                          "increasing to 1.", ap_daemons_to_start);
498         } else {
499             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00214)
500                          "StartServers of %d not allowed, increasing to 1",
501                          ap_daemons_to_start);
502         }
503         ap_daemons_to_start = 1;
504     }
505
506     if (ap_min_spare_threads < 1) {
507         if (startup) {
508             ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00215)
509                          "WARNING: MinSpareThreads of %d not allowed, "
510                          "increasing to 1", ap_min_spare_threads);
511             ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,
512                          " to avoid almost certain server failure.");
513             ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,
514                          " Please read the documentation.");
515         } else {
516             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00216)
517                          "MinSpareThreads of %d not allowed, increasing to 1",
518                          ap_min_spare_threads);
519         }
520         ap_min_spare_threads = 1;
521     }
522
523     return OK;
524 }
525
526
527
528 static void mpmt_os2_hooks(apr_pool_t *p)
529 {
530     ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
531     ap_hook_check_config(mpmt_os2_check_config, NULL, NULL, APR_HOOK_MIDDLE);
532     ap_hook_mpm(mpmt_os2_run, NULL, NULL, APR_HOOK_MIDDLE);
533     ap_hook_mpm_query(mpmt_os2_query, NULL, NULL, APR_HOOK_MIDDLE);
534     ap_hook_mpm_get_name(mpmt_os2_get_name, NULL, NULL, APR_HOOK_MIDDLE);
535 }
536
537
538
539 static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)
540 {
541     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
542
543     if (err != NULL) {
544         return err;
545     }
546
547     ap_daemons_to_start = atoi(arg);
548     return NULL;
549 }
550
551
552
553 static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy,
554                                          const char *arg)
555 {
556     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
557
558     if (err != NULL) {
559         return err;
560     }
561
562     ap_min_spare_threads = atoi(arg);
563     return NULL;
564 }
565
566
567
568 static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy,
569                                          const char *arg)
570 {
571     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
572
573     if (err != NULL) {
574         return err;
575     }
576
577     ap_max_spare_threads = atoi(arg);
578     return NULL;
579 }
580
581
582
583 static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg)
584 {
585     return NULL;
586 }
587
588
589
590 static const command_rec mpmt_os2_cmds[] = {
591 LISTEN_COMMANDS,
592 AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF,
593   "Number of child processes launched at server startup" ),
594 AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF,
595   "Minimum number of idle children, to handle request spikes"),
596 AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF,
597   "Maximum number of idle children"),
598 AP_INIT_TAKE1("User", ignore_cmd, NULL, RSRC_CONF,
599   "Not applicable on this platform"),
600 AP_INIT_TAKE1("Group", ignore_cmd, NULL, RSRC_CONF,
601   "Not applicable on this platform"),
602 AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \
603   "Not applicable on this platform"),
604 { NULL }
605 };
606
607 AP_DECLARE_MODULE(mpm_mpmt_os2) = {
608     MPM20_MODULE_STUFF,
609     NULL,            /* hook to run before apache parses args */
610     NULL,            /* create per-directory config structure */
611     NULL,            /* merge per-directory config structures */
612     NULL,            /* create per-server config structure */
613     NULL,            /* merge per-server config structures */
614     mpmt_os2_cmds,   /* command apr_table_t */
615     mpmt_os2_hooks,  /* register_hooks */
616 };