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#define CORE_PRIVATE
18#define INCL_NOPMAPI
19#define INCL_DOS
20#define INCL_DOSERRORS
21
22#include "ap_config.h"
23#include "httpd.h"
24#include "mpm_default.h"
25#include "http_main.h"
26#include "http_log.h"
27#include "http_config.h"
28#include "http_core.h"  /* for get_remote_host */
29#include "http_connection.h"
30#include "mpm.h"
31#include "ap_mpm.h"
32#include "ap_listen.h"
33#include "apr_portable.h"
34#include "apr_poll.h"
35#include "mpm_common.h"
36#include "apr_strings.h"
37#include <os2.h>
38#include <process.h>
39
40/* XXXXXX move these to header file private to this MPM */
41
42/* We don't need many processes,
43 * they're only for redundancy in the event of a crash
44 */
45#define HARD_SERVER_LIMIT 10
46
47/* Limit on the total number of threads per process
48 */
49#ifndef HARD_THREAD_LIMIT
50#define HARD_THREAD_LIMIT 256
51#endif
52
53#define ID_FROM_CHILD_THREAD(c, t)    ((c * HARD_THREAD_LIMIT) + t)
54
55typedef struct {
56    apr_pool_t *pconn;
57    apr_socket_t *conn_sd;
58} worker_args_t;
59
60#define WORKTYPE_CONN 0
61#define WORKTYPE_EXIT 1
62
63static apr_pool_t *pchild = NULL;
64static int child_slot;
65static int shutdown_pending = 0;
66extern int ap_my_generation;
67static int volatile is_graceful = 1;
68HEV shutdown_event; /* signaled when this child is shutting down */
69
70/* grab some MPM globals */
71extern int ap_min_spare_threads;
72extern int ap_max_spare_threads;
73extern HMTX ap_mpm_accept_mutex;
74
75static void worker_main(void *vpArg);
76static void clean_child_exit(int code);
77static void set_signals();
78static void server_maintenance(void *vpArg);
79
80
81static void clean_child_exit(int code)
82{
83    if (pchild) {
84        apr_pool_destroy(pchild);
85    }
86
87    exit(code);
88}
89
90
91
92void ap_mpm_child_main(apr_pool_t *pconf)
93{
94    ap_listen_rec *lr = NULL;
95    int requests_this_child = 0;
96    int rv = 0;
97    unsigned long ulTimes;
98    int my_pid = getpid();
99    ULONG rc, c;
100    HQUEUE workq;
101    apr_pollset_t *pollset;
102    int num_listeners;
103    TID server_maint_tid;
104    void *sb_mem;
105
106    /* Stop Ctrl-C/Ctrl-Break signals going to child processes */
107    DosSetSignalExceptionFocus(0, &ulTimes);
108    set_signals();
109
110    /* Create pool for child */
111    apr_pool_create(&pchild, pconf);
112
113    ap_run_child_init(pchild, ap_server_conf);
114
115    /* Create an event semaphore used to trigger other threads to shutdown */
116    rc = DosCreateEventSem(NULL, &shutdown_event, 0, FALSE);
117
118    if (rc) {
119        ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
120                     "unable to create shutdown semaphore, exiting");
121        clean_child_exit(APEXIT_CHILDFATAL);
122    }
123
124    /* Gain access to the scoreboard. */
125    rc = DosGetNamedSharedMem(&sb_mem, ap_scoreboard_fname,
126                              PAG_READ|PAG_WRITE);
127
128    if (rc) {
129        ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
130                     "scoreboard not readable in child, exiting");
131        clean_child_exit(APEXIT_CHILDFATAL);
132    }
133
134    ap_calc_scoreboard_size();
135    ap_init_scoreboard(sb_mem);
136
137    /* Gain access to the accpet mutex */
138    rc = DosOpenMutexSem(NULL, &ap_mpm_accept_mutex);
139
140    if (rc) {
141        ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
142                     "accept mutex couldn't be accessed in child, exiting");
143        clean_child_exit(APEXIT_CHILDFATAL);
144    }
145
146    /* Find our pid in the scoreboard so we know what slot our parent allocated us */
147    for (child_slot = 0; ap_scoreboard_image->parent[child_slot].pid != my_pid && child_slot < HARD_SERVER_LIMIT; child_slot++);
148
149    if (child_slot == HARD_SERVER_LIMIT) {
150        ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf,
151                     "child pid not found in scoreboard, exiting");
152        clean_child_exit(APEXIT_CHILDFATAL);
153    }
154
155    ap_my_generation = ap_scoreboard_image->parent[child_slot].generation;
156    memset(ap_scoreboard_image->servers[child_slot], 0, sizeof(worker_score) * HARD_THREAD_LIMIT);
157
158    /* Set up an OS/2 queue for passing connections & termination requests
159     * to worker threads
160     */
161    rc = DosCreateQueue(&workq, QUE_FIFO, apr_psprintf(pchild, "/queues/httpd/work.%d", my_pid));
162
163    if (rc) {
164        ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
165                     "unable to create work queue, exiting");
166        clean_child_exit(APEXIT_CHILDFATAL);
167    }
168
169    /* Create initial pool of worker threads */
170    for (c = 0; c < ap_min_spare_threads; c++) {
171//        ap_scoreboard_image->servers[child_slot][c].tid = _beginthread(worker_main, NULL, 128*1024, (void *)c);
172    }
173
174    /* Start maintenance thread */
175    server_maint_tid = _beginthread(server_maintenance, NULL, 32768, NULL);
176
177    /* Set up poll */
178    for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
179        num_listeners++;
180    }
181
182    apr_pollset_create(&pollset, num_listeners, pchild, 0);
183
184    for (lr = ap_listeners; lr != NULL; lr = lr->next) {
185        apr_pollfd_t pfd = { 0 };
186
187        pfd.desc_type = APR_POLL_SOCKET;
188        pfd.desc.s = lr->sd;
189        pfd.reqevents = APR_POLLIN;
190        pfd.client_data = lr;
191        apr_pollset_add(pollset, &pfd);
192    }
193
194    /* Main connection accept loop */
195    do {
196        apr_pool_t *pconn;
197        worker_args_t *worker_args;
198        int last_poll_idx = 0;
199
200        apr_pool_create(&pconn, pchild);
201        worker_args = apr_palloc(pconn, sizeof(worker_args_t));
202        worker_args->pconn = pconn;
203
204        if (num_listeners == 1) {
205            rv = apr_socket_accept(&worker_args->conn_sd, ap_listeners->sd, pconn);
206        } else {
207            const apr_pollfd_t *poll_results;
208            apr_int32_t num_poll_results;
209
210            rc = DosRequestMutexSem(ap_mpm_accept_mutex, SEM_INDEFINITE_WAIT);
211
212            if (shutdown_pending) {
213                DosReleaseMutexSem(ap_mpm_accept_mutex);
214                break;
215            }
216
217            rv = APR_FROM_OS_ERROR(rc);
218
219            if (rv == APR_SUCCESS) {
220                rv = apr_pollset_poll(pollset, -1, &num_poll_results, &poll_results);
221                DosReleaseMutexSem(ap_mpm_accept_mutex);
222            }
223
224            if (rv == APR_SUCCESS) {
225                if (last_poll_idx >= num_listeners) {
226                    last_poll_idx = 0;
227                }
228
229                lr = poll_results[last_poll_idx++].client_data;
230                rv = apr_socket_accept(&worker_args->conn_sd, lr->sd, pconn);
231                last_poll_idx++;
232            }
233        }
234
235        if (rv != APR_SUCCESS) {
236            if (!APR_STATUS_IS_EINTR(rv)) {
237                ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,
238                             "apr_socket_accept");
239                clean_child_exit(APEXIT_CHILDFATAL);
240            }
241        } else {
242            DosWriteQueue(workq, WORKTYPE_CONN, sizeof(worker_args_t), worker_args, 0);
243            requests_this_child++;
244        }
245
246        if (ap_max_requests_per_child != 0 && requests_this_child >= ap_max_requests_per_child)
247            break;
248    } while (!shutdown_pending && ap_my_generation == ap_scoreboard_image->global->running_generation);
249
250    ap_scoreboard_image->parent[child_slot].quiescing = 1;
251    DosPostEventSem(shutdown_event);
252    DosWaitThread(&server_maint_tid, DCWW_WAIT);
253
254    if (is_graceful) {
255        char someleft;
256
257        /* tell our worker threads to exit */
258        for (c=0; c<HARD_THREAD_LIMIT; c++) {
259            if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
260                DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0);
261            }
262        }
263
264        do {
265            someleft = 0;
266
267            for (c=0; c<HARD_THREAD_LIMIT; c++) {
268                if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
269                    someleft = 1;
270                    DosSleep(1000);
271                    break;
272                }
273            }
274        } while (someleft);
275    } else {
276        DosPurgeQueue(workq);
277
278        for (c=0; c<HARD_THREAD_LIMIT; c++) {
279            if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
280                DosKillThread(ap_scoreboard_image->servers[child_slot][c].tid);
281            }
282        }
283    }
284
285    apr_pool_destroy(pchild);
286}
287
288
289
290void add_worker()
291{
292    int thread_slot;
293
294    /* Find a free thread slot */
295    for (thread_slot=0; thread_slot < HARD_THREAD_LIMIT; thread_slot++) {
296        if (ap_scoreboard_image->servers[child_slot][thread_slot].status == SERVER_DEAD) {
297            ap_scoreboard_image->servers[child_slot][thread_slot].status = SERVER_STARTING;
298            ap_scoreboard_image->servers[child_slot][thread_slot].tid =
299                _beginthread(worker_main, NULL, 128*1024, (void *)thread_slot);
300            break;
301        }
302    }
303}
304
305
306
307ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec,
308                                        EXCEPTIONREGISTRATIONRECORD *pRegRec,
309                                        CONTEXTRECORD *pContext,
310                                        PVOID p)
311{
312    int c;
313
314    if (pReportRec->fHandlerFlags & EH_NESTED_CALL) {
315        return XCPT_CONTINUE_SEARCH;
316    }
317
318    if (pReportRec->ExceptionNum == XCPT_ACCESS_VIOLATION ||
319        pReportRec->ExceptionNum == XCPT_INTEGER_DIVIDE_BY_ZERO) {
320        ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf,
321                     "caught exception in worker thread, initiating child shutdown pid=%d", getpid());
322        for (c=0; c<HARD_THREAD_LIMIT; c++) {
323            if (ap_scoreboard_image->servers[child_slot][c].tid == _gettid()) {
324                ap_scoreboard_image->servers[child_slot][c].status = SERVER_DEAD;
325                break;
326            }
327        }
328
329        /* Shut down process ASAP, it could be quite unhealthy & leaking resources */
330        shutdown_pending = 1;
331        ap_scoreboard_image->parent[child_slot].quiescing = 1;
332        kill(getpid(), SIGHUP);
333        DosUnwindException(UNWIND_ALL, 0, 0);
334    }
335
336    return XCPT_CONTINUE_SEARCH;
337}
338
339
340
341static void worker_main(void *vpArg)
342{
343    long conn_id;
344    conn_rec *current_conn;
345    apr_pool_t *pconn;
346    apr_allocator_t *allocator;
347    apr_bucket_alloc_t *bucket_alloc;
348    worker_args_t *worker_args;
349    HQUEUE workq;
350    PID owner;
351    int rc;
352    REQUESTDATA rd;
353    ULONG len;
354    BYTE priority;
355    int thread_slot = (int)vpArg;
356    EXCEPTIONREGISTRATIONRECORD reg_rec = { NULL, thread_exception_handler };
357    ap_sb_handle_t *sbh;
358
359    /* Trap exceptions in this thread so we don't take down the whole process */
360    DosSetExceptionHandler( &reg_rec );
361
362    rc = DosOpenQueue(&owner, &workq,
363                      apr_psprintf(pchild, "/queues/httpd/work.%d", getpid()));
364
365    if (rc) {
366        ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
367                     "unable to open work queue, exiting");
368        ap_scoreboard_image->servers[child_slot][thread_slot].tid = 0;
369    }
370
371    conn_id = ID_FROM_CHILD_THREAD(child_slot, thread_slot);
372    ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_READY,
373                                        NULL);
374
375    apr_allocator_create(&allocator);
376    apr_allocator_max_free_set(allocator, ap_max_mem_free);
377    bucket_alloc = apr_bucket_alloc_create_ex(allocator);
378
379    while (rc = DosReadQueue(workq, &rd, &len, (PPVOID)&worker_args, 0, DCWW_WAIT, &priority, NULLHANDLE),
380           rc == 0 && rd.ulData != WORKTYPE_EXIT) {
381        pconn = worker_args->pconn;
382        ap_create_sb_handle(&sbh, pconn, child_slot, thread_slot);
383        current_conn = ap_run_create_connection(pconn, ap_server_conf,
384                                                worker_args->conn_sd, conn_id,
385                                                sbh, bucket_alloc);
386
387        if (current_conn) {
388            ap_process_connection(current_conn, worker_args->conn_sd);
389            ap_lingering_close(current_conn);
390        }
391
392        apr_pool_destroy(pconn);
393        ap_update_child_status_from_indexes(child_slot, thread_slot,
394                                            SERVER_READY, NULL);
395    }
396
397    ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_DEAD,
398                                        NULL);
399
400    apr_bucket_alloc_destroy(bucket_alloc);
401    apr_allocator_destroy(allocator);
402}
403
404
405
406static void server_maintenance(void *vpArg)
407{
408    int num_idle, num_needed;
409    ULONG num_pending = 0;
410    int threadnum;
411    HQUEUE workq;
412    ULONG rc;
413    PID owner;
414
415    rc = DosOpenQueue(&owner, &workq,
416                      apr_psprintf(pchild, "/queues/httpd/work.%d", getpid()));
417
418    if (rc) {
419        ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
420                     "unable to open work queue in maintenance thread");
421        return;
422    }
423
424    do {
425        for (num_idle=0, threadnum=0; threadnum < HARD_THREAD_LIMIT; threadnum++) {
426            num_idle += ap_scoreboard_image->servers[child_slot][threadnum].status == SERVER_READY;
427        }
428
429        DosQueryQueue(workq, &num_pending);
430        num_needed = ap_min_spare_threads - num_idle + num_pending;
431
432        if (num_needed > 0) {
433            for (threadnum=0; threadnum < num_needed; threadnum++) {
434                add_worker();
435            }
436        }
437
438        if (num_idle - num_pending > ap_max_spare_threads) {
439            DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0);
440        }
441    } while (DosWaitEventSem(shutdown_event, 500) == ERROR_TIMEOUT);
442}
443
444
445
446/* Signal handling routines */
447
448static void sig_term(int sig)
449{
450    shutdown_pending = 1;
451    is_graceful = 0;
452    signal(SIGTERM, SIG_DFL);
453}
454
455
456
457static void sig_hup(int sig)
458{
459    shutdown_pending = 1;
460    is_graceful = 1;
461}
462
463
464
465static void set_signals()
466{
467    struct sigaction sa;
468
469    sigemptyset(&sa.sa_mask);
470    sa.sa_flags = 0;
471    sa.sa_handler = sig_term;
472
473    if (sigaction(SIGTERM, &sa, NULL) < 0)
474        ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
475
476    sa.sa_handler = sig_hup;
477
478    if (sigaction(SIGHUP, &sa, NULL) < 0)
479        ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)");
480}
481