491 lines
15 KiB
C
491 lines
15 KiB
C
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define INCL_NOPMAPI
|
|
#define INCL_DOS
|
|
#define INCL_DOSERRORS
|
|
|
|
#include "ap_config.h"
|
|
#include "httpd.h"
|
|
#include "mpm_default.h"
|
|
#include "http_main.h"
|
|
#include "http_log.h"
|
|
#include "http_config.h"
|
|
#include "http_core.h" /* for get_remote_host */
|
|
#include "http_connection.h"
|
|
#include "scoreboard.h"
|
|
#include "ap_mpm.h"
|
|
#include "ap_listen.h"
|
|
#include "apr_portable.h"
|
|
#include "apr_poll.h"
|
|
#include "mpm_common.h"
|
|
#include "apr_strings.h"
|
|
#include <os2.h>
|
|
#include <process.h>
|
|
|
|
APLOG_USE_MODULE(mpm_mpmt_os2);
|
|
|
|
/* XXXXXX move these to header file private to this MPM */
|
|
|
|
/* We don't need many processes,
|
|
* they're only for redundancy in the event of a crash
|
|
*/
|
|
#define HARD_SERVER_LIMIT 10
|
|
|
|
/* Limit on the total number of threads per process
|
|
*/
|
|
#ifndef HARD_THREAD_LIMIT
|
|
#define HARD_THREAD_LIMIT 256
|
|
#endif
|
|
|
|
#define ID_FROM_CHILD_THREAD(c, t) ((c * HARD_THREAD_LIMIT) + t)
|
|
|
|
typedef struct {
|
|
apr_pool_t *pconn;
|
|
apr_socket_t *conn_sd;
|
|
} worker_args_t;
|
|
|
|
#define WORKTYPE_CONN 0
|
|
#define WORKTYPE_EXIT 1
|
|
|
|
static apr_pool_t *pchild = NULL;
|
|
static int child_slot;
|
|
static int shutdown_pending = 0;
|
|
extern int ap_my_generation;
|
|
static int volatile is_graceful = 1;
|
|
HEV shutdown_event; /* signaled when this child is shutting down */
|
|
|
|
/* grab some MPM globals */
|
|
extern int ap_min_spare_threads;
|
|
extern int ap_max_spare_threads;
|
|
extern HMTX ap_mpm_accept_mutex;
|
|
|
|
static void worker_main(void *vpArg);
|
|
static void clean_child_exit(int code);
|
|
static void set_signals();
|
|
static void server_maintenance(void *vpArg);
|
|
|
|
|
|
static void clean_child_exit(int code)
|
|
{
|
|
if (pchild) {
|
|
apr_pool_destroy(pchild);
|
|
}
|
|
|
|
exit(code);
|
|
}
|
|
|
|
|
|
|
|
void ap_mpm_child_main(apr_pool_t *pconf)
|
|
{
|
|
ap_listen_rec *lr = NULL;
|
|
int requests_this_child = 0;
|
|
int rv = 0;
|
|
unsigned long ulTimes;
|
|
int my_pid = getpid();
|
|
ULONG rc, c;
|
|
HQUEUE workq;
|
|
apr_pollset_t *pollset;
|
|
int num_listeners;
|
|
TID server_maint_tid;
|
|
void *sb_mem;
|
|
|
|
/* Stop Ctrl-C/Ctrl-Break signals going to child processes */
|
|
DosSetSignalExceptionFocus(0, &ulTimes);
|
|
set_signals();
|
|
|
|
/* Create pool for child */
|
|
apr_pool_create(&pchild, pconf);
|
|
apr_pool_tag(pchild, "pchild");
|
|
|
|
ap_run_child_init(pchild, ap_server_conf);
|
|
|
|
/* Create an event semaphore used to trigger other threads to shutdown */
|
|
rc = DosCreateEventSem(NULL, &shutdown_event, 0, FALSE);
|
|
|
|
if (rc) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00189)
|
|
"unable to create shutdown semaphore, exiting");
|
|
clean_child_exit(APEXIT_CHILDFATAL);
|
|
}
|
|
|
|
/* Gain access to the scoreboard. */
|
|
rc = DosGetNamedSharedMem(&sb_mem, ap_scoreboard_fname,
|
|
PAG_READ|PAG_WRITE);
|
|
|
|
if (rc) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00190)
|
|
"scoreboard not readable in child, exiting");
|
|
clean_child_exit(APEXIT_CHILDFATAL);
|
|
}
|
|
|
|
ap_calc_scoreboard_size();
|
|
ap_init_scoreboard(sb_mem);
|
|
|
|
/* Gain access to the accpet mutex */
|
|
rc = DosOpenMutexSem(NULL, &ap_mpm_accept_mutex);
|
|
|
|
if (rc) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00191)
|
|
"accept mutex couldn't be accessed in child, exiting");
|
|
clean_child_exit(APEXIT_CHILDFATAL);
|
|
}
|
|
|
|
/* Find our pid in the scoreboard so we know what slot our parent allocated us */
|
|
for (child_slot = 0; ap_scoreboard_image->parent[child_slot].pid != my_pid && child_slot < HARD_SERVER_LIMIT; child_slot++);
|
|
|
|
if (child_slot == HARD_SERVER_LIMIT) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00192)
|
|
"child pid not found in scoreboard, exiting");
|
|
clean_child_exit(APEXIT_CHILDFATAL);
|
|
}
|
|
|
|
ap_my_generation = ap_scoreboard_image->parent[child_slot].generation;
|
|
memset(ap_scoreboard_image->servers[child_slot], 0, sizeof(worker_score) * HARD_THREAD_LIMIT);
|
|
|
|
/* Set up an OS/2 queue for passing connections & termination requests
|
|
* to worker threads
|
|
*/
|
|
rc = DosCreateQueue(&workq, QUE_FIFO, apr_psprintf(pchild, "/queues/httpd/work.%d", my_pid));
|
|
|
|
if (rc) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00193)
|
|
"unable to create work queue, exiting");
|
|
clean_child_exit(APEXIT_CHILDFATAL);
|
|
}
|
|
|
|
/* Create initial pool of worker threads */
|
|
for (c = 0; c < ap_min_spare_threads; c++) {
|
|
// ap_scoreboard_image->servers[child_slot][c].tid = _beginthread(worker_main, NULL, 128*1024, (void *)c);
|
|
}
|
|
|
|
/* Start maintenance thread */
|
|
server_maint_tid = _beginthread(server_maintenance, NULL, 32768, NULL);
|
|
|
|
/* Set up poll */
|
|
for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
|
|
num_listeners++;
|
|
}
|
|
|
|
apr_pollset_create(&pollset, num_listeners, pchild, 0);
|
|
|
|
for (lr = ap_listeners; lr != NULL; lr = lr->next) {
|
|
apr_pollfd_t pfd = { 0 };
|
|
|
|
pfd.desc_type = APR_POLL_SOCKET;
|
|
pfd.desc.s = lr->sd;
|
|
pfd.reqevents = APR_POLLIN;
|
|
pfd.client_data = lr;
|
|
apr_pollset_add(pollset, &pfd);
|
|
}
|
|
|
|
/* Main connection accept loop */
|
|
do {
|
|
apr_pool_t *pconn;
|
|
worker_args_t *worker_args;
|
|
int last_poll_idx = 0;
|
|
|
|
apr_pool_create(&pconn, pchild);
|
|
apr_pool_tag(pconn, "transaction");
|
|
worker_args = apr_palloc(pconn, sizeof(worker_args_t));
|
|
worker_args->pconn = pconn;
|
|
|
|
if (num_listeners == 1) {
|
|
rv = apr_socket_accept(&worker_args->conn_sd, ap_listeners->sd, pconn);
|
|
} else {
|
|
const apr_pollfd_t *poll_results;
|
|
apr_int32_t num_poll_results;
|
|
|
|
rc = DosRequestMutexSem(ap_mpm_accept_mutex, SEM_INDEFINITE_WAIT);
|
|
|
|
if (shutdown_pending) {
|
|
DosReleaseMutexSem(ap_mpm_accept_mutex);
|
|
break;
|
|
}
|
|
|
|
rv = APR_FROM_OS_ERROR(rc);
|
|
|
|
if (rv == APR_SUCCESS) {
|
|
rv = apr_pollset_poll(pollset, -1, &num_poll_results, &poll_results);
|
|
DosReleaseMutexSem(ap_mpm_accept_mutex);
|
|
}
|
|
|
|
if (rv == APR_SUCCESS) {
|
|
if (last_poll_idx >= num_listeners) {
|
|
last_poll_idx = 0;
|
|
}
|
|
|
|
lr = poll_results[last_poll_idx++].client_data;
|
|
rv = apr_socket_accept(&worker_args->conn_sd, lr->sd, pconn);
|
|
last_poll_idx++;
|
|
}
|
|
}
|
|
|
|
if (rv != APR_SUCCESS) {
|
|
if (!APR_STATUS_IS_EINTR(rv)) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00194)
|
|
"apr_socket_accept");
|
|
clean_child_exit(APEXIT_CHILDFATAL);
|
|
}
|
|
} else {
|
|
DosWriteQueue(workq, WORKTYPE_CONN, sizeof(worker_args_t), worker_args, 0);
|
|
requests_this_child++;
|
|
}
|
|
|
|
if (ap_max_requests_per_child != 0 && requests_this_child >= ap_max_requests_per_child)
|
|
break;
|
|
} while (!shutdown_pending && ap_my_generation == ap_scoreboard_image->global->running_generation);
|
|
|
|
ap_scoreboard_image->parent[child_slot].quiescing = 1;
|
|
DosPostEventSem(shutdown_event);
|
|
DosWaitThread(&server_maint_tid, DCWW_WAIT);
|
|
|
|
if (is_graceful) {
|
|
char someleft;
|
|
|
|
/* tell our worker threads to exit */
|
|
for (c=0; c<HARD_THREAD_LIMIT; c++) {
|
|
if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
|
|
DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0);
|
|
}
|
|
}
|
|
|
|
do {
|
|
someleft = 0;
|
|
|
|
for (c=0; c<HARD_THREAD_LIMIT; c++) {
|
|
if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
|
|
someleft = 1;
|
|
DosSleep(1000);
|
|
break;
|
|
}
|
|
}
|
|
} while (someleft);
|
|
} else {
|
|
DosPurgeQueue(workq);
|
|
|
|
for (c=0; c<HARD_THREAD_LIMIT; c++) {
|
|
if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
|
|
DosKillThread(ap_scoreboard_image->servers[child_slot][c].tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
apr_pool_destroy(pchild);
|
|
}
|
|
|
|
|
|
|
|
void add_worker()
|
|
{
|
|
int thread_slot;
|
|
int stacksize = ap_thread_stacksize == 0 ? 128*1024 : ap_thread_stacksize;
|
|
|
|
/* Find a free thread slot */
|
|
for (thread_slot=0; thread_slot < HARD_THREAD_LIMIT; thread_slot++) {
|
|
if (ap_scoreboard_image->servers[child_slot][thread_slot].status == SERVER_DEAD) {
|
|
ap_scoreboard_image->servers[child_slot][thread_slot].status = SERVER_STARTING;
|
|
ap_scoreboard_image->servers[child_slot][thread_slot].tid =
|
|
_beginthread(worker_main, NULL, stacksize, (void *)thread_slot);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec,
|
|
EXCEPTIONREGISTRATIONRECORD *pRegRec,
|
|
CONTEXTRECORD *pContext,
|
|
PVOID p)
|
|
{
|
|
int c;
|
|
|
|
if (pReportRec->fHandlerFlags & EH_NESTED_CALL) {
|
|
return XCPT_CONTINUE_SEARCH;
|
|
}
|
|
|
|
if (pReportRec->ExceptionNum == XCPT_ACCESS_VIOLATION ||
|
|
pReportRec->ExceptionNum == XCPT_INTEGER_DIVIDE_BY_ZERO) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00195)
|
|
"caught exception in worker thread, initiating child shutdown pid=%d", getpid());
|
|
for (c=0; c<HARD_THREAD_LIMIT; c++) {
|
|
if (ap_scoreboard_image->servers[child_slot][c].tid == _gettid()) {
|
|
ap_scoreboard_image->servers[child_slot][c].status = SERVER_DEAD;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Shut down process ASAP, it could be quite unhealthy & leaking resources */
|
|
shutdown_pending = 1;
|
|
ap_scoreboard_image->parent[child_slot].quiescing = 1;
|
|
kill(getpid(), SIGHUP);
|
|
DosUnwindException(UNWIND_ALL, 0, 0);
|
|
}
|
|
|
|
return XCPT_CONTINUE_SEARCH;
|
|
}
|
|
|
|
|
|
|
|
static void worker_main(void *vpArg)
|
|
{
|
|
apr_thread_t *thd = NULL;
|
|
apr_os_thread_t osthd;
|
|
long conn_id;
|
|
conn_rec *current_conn;
|
|
apr_pool_t *pconn;
|
|
apr_allocator_t *allocator;
|
|
apr_bucket_alloc_t *bucket_alloc;
|
|
worker_args_t *worker_args;
|
|
HQUEUE workq;
|
|
PID owner;
|
|
int rc;
|
|
REQUESTDATA rd;
|
|
ULONG len;
|
|
BYTE priority;
|
|
int thread_slot = (int)vpArg;
|
|
EXCEPTIONREGISTRATIONRECORD reg_rec = { NULL, thread_exception_handler };
|
|
ap_sb_handle_t *sbh;
|
|
|
|
/* Trap exceptions in this thread so we don't take down the whole process */
|
|
DosSetExceptionHandler( ®_rec );
|
|
|
|
osthd = apr_os_thread_current();
|
|
apr_os_thread_put(&thd, &osthd, pchild);
|
|
|
|
rc = DosOpenQueue(&owner, &workq,
|
|
apr_psprintf(pchild, "/queues/httpd/work.%d", getpid()));
|
|
|
|
if (rc) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00196)
|
|
"unable to open work queue, exiting");
|
|
ap_scoreboard_image->servers[child_slot][thread_slot].tid = 0;
|
|
}
|
|
|
|
conn_id = ID_FROM_CHILD_THREAD(child_slot, thread_slot);
|
|
ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_READY,
|
|
NULL);
|
|
|
|
apr_allocator_create(&allocator);
|
|
apr_allocator_max_free_set(allocator, ap_max_mem_free);
|
|
bucket_alloc = apr_bucket_alloc_create_ex(allocator);
|
|
|
|
while (rc = DosReadQueue(workq, &rd, &len, (PPVOID)&worker_args, 0, DCWW_WAIT, &priority, NULLHANDLE),
|
|
rc == 0 && rd.ulData != WORKTYPE_EXIT) {
|
|
pconn = worker_args->pconn;
|
|
ap_create_sb_handle(&sbh, pconn, child_slot, thread_slot);
|
|
current_conn = ap_run_create_connection(pconn, ap_server_conf,
|
|
worker_args->conn_sd, conn_id,
|
|
sbh, bucket_alloc);
|
|
|
|
if (current_conn) {
|
|
current_conn->current_thread = thd;
|
|
ap_process_connection(current_conn, worker_args->conn_sd);
|
|
ap_lingering_close(current_conn);
|
|
}
|
|
|
|
apr_pool_destroy(pconn);
|
|
ap_update_child_status_from_indexes(child_slot, thread_slot,
|
|
SERVER_READY, NULL);
|
|
}
|
|
|
|
ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_DEAD,
|
|
NULL);
|
|
|
|
apr_bucket_alloc_destroy(bucket_alloc);
|
|
apr_allocator_destroy(allocator);
|
|
}
|
|
|
|
|
|
|
|
static void server_maintenance(void *vpArg)
|
|
{
|
|
int num_idle, num_needed;
|
|
ULONG num_pending = 0;
|
|
int threadnum;
|
|
HQUEUE workq;
|
|
ULONG rc;
|
|
PID owner;
|
|
|
|
rc = DosOpenQueue(&owner, &workq,
|
|
apr_psprintf(pchild, "/queues/httpd/work.%d", getpid()));
|
|
|
|
if (rc) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00197)
|
|
"unable to open work queue in maintenance thread");
|
|
return;
|
|
}
|
|
|
|
do {
|
|
for (num_idle=0, threadnum=0; threadnum < HARD_THREAD_LIMIT; threadnum++) {
|
|
num_idle += ap_scoreboard_image->servers[child_slot][threadnum].status == SERVER_READY;
|
|
}
|
|
|
|
DosQueryQueue(workq, &num_pending);
|
|
num_needed = ap_min_spare_threads - num_idle + num_pending;
|
|
|
|
if (num_needed > 0) {
|
|
for (threadnum=0; threadnum < num_needed; threadnum++) {
|
|
add_worker();
|
|
}
|
|
}
|
|
|
|
if (num_idle - num_pending > ap_max_spare_threads) {
|
|
DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0);
|
|
}
|
|
} while (DosWaitEventSem(shutdown_event, 500) == ERROR_TIMEOUT);
|
|
}
|
|
|
|
|
|
|
|
/* Signal handling routines */
|
|
|
|
static void sig_term(int sig)
|
|
{
|
|
shutdown_pending = 1;
|
|
is_graceful = 0;
|
|
signal(SIGTERM, SIG_DFL);
|
|
}
|
|
|
|
|
|
|
|
static void sig_hup(int sig)
|
|
{
|
|
shutdown_pending = 1;
|
|
is_graceful = 1;
|
|
}
|
|
|
|
|
|
|
|
static void set_signals()
|
|
{
|
|
struct sigaction sa;
|
|
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = 0;
|
|
sa.sa_handler = sig_term;
|
|
|
|
if (sigaction(SIGTERM, &sa, NULL) < 0)
|
|
ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00198) "sigaction(SIGTERM)");
|
|
|
|
sa.sa_handler = sig_hup;
|
|
|
|
if (sigaction(SIGHUP, &sa, NULL) < 0)
|
|
ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00199) "sigaction(SIGHUP)");
|
|
}
|