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( ®_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