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