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