From e8ec7aa8e38a93f5b034ac74cebce5de23710317 Mon Sep 17 00:00:00 2001 From: hongbotian Date: Mon, 30 Nov 2015 01:45:08 -0500 Subject: upload http JIRA: BOTTLENECK-10 Change-Id: I7598427ff904df438ce77c2819ee48ac75ffa8da Signed-off-by: hongbotian --- rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c | 1167 ++++++++++++++++++++++ 1 file changed, 1167 insertions(+) create mode 100644 rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c (limited to 'rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c') diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c b/rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c new file mode 100644 index 00000000..266d0b3c --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c @@ -0,0 +1,1167 @@ +/* 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. + */ + +#ifdef WIN32 + +#define CORE_PRIVATE +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include +#include "apr_atomic.h" + +/* shared with mpm_winnt.c */ +extern DWORD my_pid; + +/* used by parent to signal the child to start and exit */ +/* shared with mpm_winnt.c, but should be private to child.c */ +apr_proc_mutex_t *start_mutex; +HANDLE exit_event; + +/* child_main() should never need to modify is_graceful!?! */ +extern int volatile is_graceful; + +/* Queue for managing the passing of COMP_CONTEXTs between + * the accept and worker threads. + */ +static apr_pool_t *pchild; +static int shutdown_in_progress = 0; +static int workers_may_exit = 0; +static unsigned int g_blocked_threads = 0; +static HANDLE max_requests_per_child_event; + +static apr_thread_mutex_t *child_lock; +static apr_thread_mutex_t *qlock; +static PCOMP_CONTEXT qhead = NULL; +static PCOMP_CONTEXT qtail = NULL; +static int num_completion_contexts = 0; +static int max_num_completion_contexts = 0; +static HANDLE ThreadDispatchIOCP = NULL; +static HANDLE qwait_event = NULL; + + +AP_DECLARE(void) mpm_recycle_completion_context(PCOMP_CONTEXT context) +{ + /* Recycle the completion context. + * - clear the ptrans pool + * - put the context on the queue to be consumed by the accept thread + * Note: + * context->accept_socket may be in a disconnected but reusable + * state so -don't- close it. + */ + if (context) { + apr_pool_clear(context->ptrans); + context->ba = apr_bucket_alloc_create(context->ptrans); + context->next = NULL; + ResetEvent(context->Overlapped.hEvent); + apr_thread_mutex_lock(qlock); + if (qtail) { + qtail->next = context; + } else { + qhead = context; + SetEvent(qwait_event); + } + qtail = context; + apr_thread_mutex_unlock(qlock); + } +} + +AP_DECLARE(PCOMP_CONTEXT) mpm_get_completion_context(void) +{ + apr_status_t rv; + PCOMP_CONTEXT context = NULL; + + while (1) { + /* Grab a context off the queue */ + apr_thread_mutex_lock(qlock); + if (qhead) { + context = qhead; + qhead = qhead->next; + if (!qhead) + qtail = NULL; + } else { + ResetEvent(qwait_event); + } + apr_thread_mutex_unlock(qlock); + + if (!context) { + /* We failed to grab a context off the queue, consider allocating + * a new one out of the child pool. There may be up to + * (ap_threads_per_child + num_listeners) contexts in the system + * at once. + */ + if (num_completion_contexts >= max_num_completion_contexts) { + /* All workers are busy, need to wait for one */ + static int reported = 0; + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + "Server ran out of threads to serve requests. Consider " + "raising the ThreadsPerChild setting"); + reported = 1; + } + + /* Wait for a worker to free a context. Once per second, give + * the caller a chance to check for shutdown. If the wait + * succeeds, get the context off the queue. It must be available, + * since there's only one consumer. + */ + rv = WaitForSingleObject(qwait_event, 1000); + if (rv == WAIT_OBJECT_0) + continue; + else /* Hopefully, WAIT_TIMEOUT */ + return NULL; + } else { + /* Allocate another context. + * Note: + * Multiple failures in the next two steps will cause the pchild pool + * to 'leak' storage. I don't think this is worth fixing... + */ + apr_allocator_t *allocator; + + apr_thread_mutex_lock(child_lock); + context = (PCOMP_CONTEXT) apr_pcalloc(pchild, sizeof(COMP_CONTEXT)); + + context->Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (context->Overlapped.hEvent == NULL) { + /* Hopefully this is a temporary condition ... */ + ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_os_error(), ap_server_conf, + "mpm_get_completion_context: CreateEvent failed."); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + + /* Create the tranaction pool */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + rv = apr_pool_create_ex(&context->ptrans, pchild, NULL, allocator); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_WARNING, rv, ap_server_conf, + "mpm_get_completion_context: Failed to create the transaction pool."); + CloseHandle(context->Overlapped.hEvent); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + apr_allocator_owner_set(allocator, context->ptrans); + apr_pool_tag(context->ptrans, "transaction"); + context->accept_socket = INVALID_SOCKET; + context->ba = apr_bucket_alloc_create(context->ptrans); + apr_atomic_inc(&num_completion_contexts); + + apr_thread_mutex_unlock(child_lock); + break; + } + } else { + /* Got a context from the queue */ + break; + } + } + + return context; +} + +AP_DECLARE(apr_status_t) mpm_post_completion_context(PCOMP_CONTEXT context, + io_state_e state) +{ + LPOVERLAPPED pOverlapped; + if (context) + pOverlapped = &context->Overlapped; + else + pOverlapped = NULL; + + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, state, pOverlapped); + return APR_SUCCESS; +} + + +/* + * find_ready_listener() + * Only used by Win9* and should go away when the win9*_accept() function is + * reimplemented using apr_poll(). + */ +static ap_listen_rec *head_listener; + +static APR_INLINE ap_listen_rec *find_ready_listener(fd_set * main_fds) +{ + ap_listen_rec *lr; + SOCKET nsd; + + lr = head_listener; + do { + apr_os_sock_get(&nsd, lr->sd); + if (FD_ISSET(nsd, main_fds)) { + head_listener = lr->next; + if (!head_listener) { + head_listener = ap_listeners; + } + return lr; + } + lr = lr->next; + if (!lr) { + lr = ap_listeners; + } + } while (lr != head_listener); + return NULL; +} + + +/* Windows 9x specific code... + * Accept processing for on Windows 95/98 uses a producer/consumer queue + * model. A single thread accepts connections and queues the accepted socket + * to the accept queue for consumption by a pool of worker threads. + * + * win9x_accept() + * The accept threads runs this function, which accepts connections off + * the network and calls add_job() to queue jobs to the accept_queue. + * add_job()/remove_job() + * Add or remove an accepted socket from the list of sockets + * connected to clients. allowed_globals.jobmutex protects + * against multiple concurrent access to the linked list of jobs. + * win9x_get_connection() + * Calls remove_job() to pull a job from the accept queue. All the worker + * threads block on remove_job. + */ + +typedef struct joblist_s { + struct joblist_s *next; + int sock; +} joblist; + +typedef struct globals_s { + HANDLE jobsemaphore; + joblist *jobhead; + joblist *jobtail; + apr_thread_mutex_t *jobmutex; + int jobcount; +} globals; + +globals allowed_globals = {NULL, NULL, NULL, NULL, 0}; + +#define MAX_SELECT_ERRORS 100 + + +static void add_job(int sock) +{ + joblist *new_job; + + new_job = (joblist *) malloc(sizeof(joblist)); + if (new_job == NULL) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "Ouch! Out of memory in add_job()!"); + return; + } + new_job->next = NULL; + new_job->sock = sock; + + apr_thread_mutex_lock(allowed_globals.jobmutex); + + if (allowed_globals.jobtail != NULL) + allowed_globals.jobtail->next = new_job; + allowed_globals.jobtail = new_job; + if (!allowed_globals.jobhead) + allowed_globals.jobhead = new_job; + allowed_globals.jobcount++; + ReleaseSemaphore(allowed_globals.jobsemaphore, 1, NULL); + + apr_thread_mutex_unlock(allowed_globals.jobmutex); +} + + +static int remove_job(void) +{ + joblist *job; + int sock; + + WaitForSingleObject(allowed_globals.jobsemaphore, INFINITE); + apr_thread_mutex_lock(allowed_globals.jobmutex); + + if (shutdown_in_progress && !allowed_globals.jobhead) { + apr_thread_mutex_unlock(allowed_globals.jobmutex); + return (INVALID_SOCKET); + } + job = allowed_globals.jobhead; + ap_assert(job); + allowed_globals.jobhead = job->next; + if (allowed_globals.jobhead == NULL) + allowed_globals.jobtail = NULL; + apr_thread_mutex_unlock(allowed_globals.jobmutex); + sock = job->sock; + free(job); + + return (sock); +} + + +static unsigned int __stdcall win9x_accept(void * dummy) +{ + struct timeval tv; + fd_set main_fds; + int wait_time = 1; + int csd; + SOCKET nsd = INVALID_SOCKET; + struct sockaddr_in sa_client; + int count_select_errors = 0; + int rc; + int clen; + ap_listen_rec *lr; + struct fd_set listenfds; + SOCKET listenmaxfd = INVALID_SOCKET; + + /* Setup the listeners + * ToDo: Use apr_poll() + */ + FD_ZERO(&listenfds); + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->sd != NULL) { + apr_os_sock_get(&nsd, lr->sd); + FD_SET(nsd, &listenfds); + if (listenmaxfd == INVALID_SOCKET || nsd > listenmaxfd) { + listenmaxfd = nsd; + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "Child %d: Listening on port %d.", my_pid, lr->bind_addr->port); + } + } + + head_listener = ap_listeners; + + while (!shutdown_in_progress) { + tv.tv_sec = wait_time; + tv.tv_usec = 0; + memcpy(&main_fds, &listenfds, sizeof(fd_set)); + + rc = select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv); + + if (rc == 0 || (rc == SOCKET_ERROR && APR_STATUS_IS_EINTR(apr_get_netos_error()))) { + count_select_errors = 0; /* reset count of errors */ + continue; + } + else if (rc == SOCKET_ERROR) { + /* A "real" error occurred, log it and increment the count of + * select errors. This count is used to ensure we don't go into + * a busy loop of continuous errors. + */ + ap_log_error(APLOG_MARK, APLOG_INFO, apr_get_netos_error(), ap_server_conf, + "select failed with error %d", apr_get_netos_error()); + count_select_errors++; + if (count_select_errors > MAX_SELECT_ERRORS) { + shutdown_in_progress = 1; + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), ap_server_conf, + "Too many errors in select loop. Child process exiting."); + break; + } + } else { + ap_listen_rec *lr; + + lr = find_ready_listener(&main_fds); + if (lr != NULL) { + /* fetch the native socket descriptor */ + apr_os_sock_get(&nsd, lr->sd); + } + } + + do { + clen = sizeof(sa_client); + csd = accept(nsd, (struct sockaddr *) &sa_client, &clen); + } while (csd < 0 && APR_STATUS_IS_EINTR(apr_get_netos_error())); + + if (csd < 0) { + if (APR_STATUS_IS_ECONNABORTED(apr_get_netos_error())) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), ap_server_conf, + "accept: (client socket)"); + } + } + else { + add_job(csd); + } + } + SetEvent(exit_event); + return 0; +} + + +static PCOMP_CONTEXT win9x_get_connection(PCOMP_CONTEXT context) +{ + apr_os_sock_info_t sockinfo; + int len; + + if (context == NULL) { + /* allocate the completion context and the transaction pool */ + apr_allocator_t *allocator; + apr_thread_mutex_lock(child_lock); + context = apr_pcalloc(pchild, sizeof(COMP_CONTEXT)); + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + apr_pool_create_ex(&context->ptrans, pchild, NULL, allocator); + apr_allocator_owner_set(allocator, context->ptrans); + apr_pool_tag(context->ptrans, "transaction"); + apr_thread_mutex_unlock(child_lock); + } + + while (1) { + apr_pool_clear(context->ptrans); + context->ba = apr_bucket_alloc_create(context->ptrans); + context->accept_socket = remove_job(); + if (context->accept_socket == INVALID_SOCKET) { + return NULL; + } + len = sizeof(struct sockaddr); + context->sa_server = apr_palloc(context->ptrans, len); + if (getsockname(context->accept_socket, + context->sa_server, &len)== SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "getsockname failed"); + continue; + } + len = sizeof(struct sockaddr); + context->sa_client = apr_palloc(context->ptrans, len); + if ((getpeername(context->accept_socket, + context->sa_client, &len)) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "getpeername failed"); + memset(&context->sa_client, '\0', sizeof(context->sa_client)); + } + sockinfo.os_sock = &context->accept_socket; + sockinfo.local = context->sa_server; + sockinfo.remote = context->sa_client; + sockinfo.family = APR_INET; + sockinfo.type = SOCK_STREAM; + apr_os_sock_make(&context->sock, &sockinfo, context->ptrans); + + return context; + } +} + + +/* Windows NT/2000 specific code... + * Accept processing for on Windows NT uses a producer/consumer queue + * model. An accept thread accepts connections off the network then issues + * PostQueuedCompletionStatus() to awake a thread blocked on the ThreadDispatch + * IOCompletionPort. + * + * winnt_accept() + * One or more accept threads run in this function, each of which accepts + * connections off the network and calls PostQueuedCompletionStatus() to + * queue an io completion packet to the ThreadDispatch IOCompletionPort. + * winnt_get_connection() + * Worker threads block on the ThreadDispatch IOCompletionPort awaiting + * connections to service. + */ +#define MAX_ACCEPTEX_ERR_COUNT 250 +static unsigned int __stdcall winnt_accept(void *lr_) +{ + ap_listen_rec *lr = (ap_listen_rec *)lr_; + apr_os_sock_info_t sockinfo; + PCOMP_CONTEXT context = NULL; + DWORD BytesRead; + SOCKET nlsd; + int rv, err_count = 0; + + apr_os_sock_get(&nlsd, lr->sd); + + while (!shutdown_in_progress) { + if (!context) { + context = mpm_get_completion_context(); + if (!context) { + /* Temporary resource constraint? */ + Sleep(0); + continue; + } + } + + /* Create and initialize the accept socket */ + if (context->accept_socket == INVALID_SOCKET) { + context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (context->accept_socket == INVALID_SOCKET) { + /* Another temporary condition? */ + ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "winnt_accept: Failed to allocate an accept socket. " + "Temporary resource constraint? Try again."); + Sleep(100); + continue; + } + } + + /* AcceptEx on the completion context. The completion context will be + * signaled when a connection is accepted. + */ + if (!AcceptEx(nlsd, context->accept_socket, + context->buff, + 0, + PADDED_ADDR_SIZE, + PADDED_ADDR_SIZE, + &BytesRead, + &context->Overlapped)) { + rv = apr_get_netos_error(); + if ((rv == APR_FROM_OS_ERROR(WSAEINVAL)) || + (rv == APR_FROM_OS_ERROR(WSAENOTSOCK))) { + /* We can get here when: + * 1) the client disconnects early + * 2) TransmitFile does not properly recycle the accept socket (typically + * because the client disconnected) + * 3) there is VPN or Firewall software installed with buggy AcceptEx implementation + * 4) the webserver is using a dynamic address that has changed + */ + ++err_count; + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "Child %d: Encountered too many errors accepting client connections. " + "Possible causes: dynamic address renewal, or incompatible VPN or firewall software. " + "Try using the Win32DisableAcceptEx directive.", my_pid); + err_count = 0; + } + continue; + } + else if ((rv != APR_FROM_OS_ERROR(ERROR_IO_PENDING)) && + (rv != APR_FROM_OS_ERROR(WSA_IO_PENDING))) { + ++err_count; + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + "Child %d: Encountered too many errors accepting client connections. " + "Possible causes: Unknown. " + "Try using the Win32DisableAcceptEx directive.", my_pid); + err_count = 0; + } + continue; + } + + /* Wait for pending i/o. + * Wake up once per second to check for shutdown . + * XXX: We should be waiting on exit_event instead of polling + */ + while (1) { + rv = WaitForSingleObject(context->Overlapped.hEvent, 1000); + if (rv == WAIT_OBJECT_0) { + if (context->accept_socket == INVALID_SOCKET) { + /* socket already closed */ + break; + } + if (!GetOverlappedResult((HANDLE)context->accept_socket, + &context->Overlapped, + &BytesRead, FALSE)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + apr_get_os_error(), ap_server_conf, + "winnt_accept: Asynchronous AcceptEx failed."); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + } + break; + } + /* WAIT_TIMEOUT */ + if (shutdown_in_progress) { + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + break; + } + } + if (context->accept_socket == INVALID_SOCKET) { + continue; + } + } + + err_count = 0; + /* Inherit the listen socket settings. Required for + * shutdown() to work + */ + if (setsockopt(context->accept_socket, SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, (char *)&nlsd, + sizeof(nlsd))) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed."); + /* Not a failure condition. Keep running. */ + } + + /* Get the local & remote address */ + GetAcceptExSockaddrs(context->buff, + 0, + PADDED_ADDR_SIZE, + PADDED_ADDR_SIZE, + &context->sa_server, + &context->sa_server_len, + &context->sa_client, + &context->sa_client_len); + + sockinfo.os_sock = &context->accept_socket; + sockinfo.local = context->sa_server; + sockinfo.remote = context->sa_client; + sockinfo.family = APR_INET; + sockinfo.type = SOCK_STREAM; + apr_os_sock_make(&context->sock, &sockinfo, context->ptrans); + + /* When a connection is received, send an io completion notification to + * the ThreadDispatchIOCP. This function could be replaced by + * mpm_post_completion_context(), but why do an extra function call... + */ + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_CONNECTION_ACCEPTED, + &context->Overlapped); + context = NULL; + } + if (!shutdown_in_progress) { + /* Yow, hit an irrecoverable error! Tell the child to die. */ + SetEvent(exit_event); + } + ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf, + "Child %d: Accept thread exiting.", my_pid); + return 0; +} + + +static PCOMP_CONTEXT winnt_get_connection(PCOMP_CONTEXT context) +{ + int rc; + DWORD BytesRead; + DWORD CompKey; + LPOVERLAPPED pol; + + mpm_recycle_completion_context(context); + + apr_atomic_inc(&g_blocked_threads); + while (1) { + if (workers_may_exit) { + apr_atomic_dec(&g_blocked_threads); + return NULL; + } + rc = GetQueuedCompletionStatus(ThreadDispatchIOCP, &BytesRead, &CompKey, + &pol, INFINITE); + if (!rc) { + rc = apr_get_os_error(); + ap_log_error(APLOG_MARK,APLOG_DEBUG, rc, ap_server_conf, + "Child %d: GetQueuedComplationStatus returned %d", my_pid, rc); + continue; + } + + switch (CompKey) { + case IOCP_CONNECTION_ACCEPTED: + context = CONTAINING_RECORD(pol, COMP_CONTEXT, Overlapped); + break; + case IOCP_SHUTDOWN: + apr_atomic_dec(&g_blocked_threads); + return NULL; + default: + apr_atomic_dec(&g_blocked_threads); + return NULL; + } + break; + } + apr_atomic_dec(&g_blocked_threads); + + return context; +} + + +/* + * worker_main() + * Main entry point for the worker threads. Worker threads block in + * win*_get_connection() awaiting a connection to service. + */ +static unsigned int __stdcall worker_main(void *thread_num_val) +{ + static int requests_this_child = 0; + PCOMP_CONTEXT context = NULL; + int thread_num = (int)thread_num_val; + ap_sb_handle_t *sbh; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, + "Child %d: Worker thread %ld starting.", my_pid, thread_num); + while (1) { + conn_rec *c; + apr_int32_t disconnected; + + ap_update_child_status_from_indexes(0, thread_num, SERVER_READY, NULL); + + /* Grab a connection off the network */ + if (use_acceptex) { + context = winnt_get_connection(context); + } + else { + context = win9x_get_connection(context); + } + if (!context) { + /* Time for the thread to exit */ + break; + } + + /* Have we hit MaxRequestPerChild connections? */ + if (ap_max_requests_per_child) { + requests_this_child++; + if (requests_this_child > ap_max_requests_per_child) { + SetEvent(max_requests_per_child_event); + } + } + + ap_create_sb_handle(&sbh, context->ptrans, 0, thread_num); + c = ap_run_create_connection(context->ptrans, ap_server_conf, + context->sock, thread_num, sbh, + context->ba); + + if (c) { + ap_process_connection(c, context->sock); + apr_socket_opt_get(context->sock, APR_SO_DISCONNECTED, + &disconnected); + if (!disconnected) { + context->accept_socket = INVALID_SOCKET; + ap_lingering_close(c); + } + else if (!use_acceptex) { + /* If the socket is disconnected but we are not using acceptex, + * we cannot reuse the socket. Disconnected sockets are removed + * from the apr_socket_t struct by apr_sendfile() to prevent the + * socket descriptor from being inadvertently closed by a call + * to apr_socket_close(), so close it directly. + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + } + } + else { + /* ap_run_create_connection closes the socket on failure */ + context->accept_socket = INVALID_SOCKET; + } + } + + ap_update_child_status_from_indexes(0, thread_num, SERVER_DEAD, + (request_rec *) NULL); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, + "Child %d: Worker thread %ld exiting.", my_pid, thread_num); + return 0; +} + + +static void cleanup_thread(HANDLE *handles, int *thread_cnt, int thread_to_clean) +{ + int i; + + CloseHandle(handles[thread_to_clean]); + for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++) + handles[i] = handles[i + 1]; + (*thread_cnt)--; +} + + +/* + * child_main() + * Entry point for the main control thread for the child process. + * This thread creates the accept thread, worker threads and + * monitors the child process for maintenance and shutdown + * events. + */ +static void create_listener_thread() +{ + int tid; + int num_listeners = 0; + if (!use_acceptex) { + _beginthreadex(NULL, 0, win9x_accept, + NULL, 0, &tid); + } else { + /* Start an accept thread per listener + * XXX: Why would we have a NULL sd in our listeners? + */ + ap_listen_rec *lr; + + /* Number of completion_contexts allowed in the system is + * (ap_threads_per_child + num_listeners). We need the additional + * completion contexts to prevent server hangs when ThreadsPerChild + * is configured to something less than or equal to the number + * of listeners. This is not a usual case, but people have + * encountered it. + * */ + for (lr = ap_listeners; lr ; lr = lr->next) { + num_listeners++; + } + max_num_completion_contexts = ap_threads_per_child + num_listeners; + + /* Now start a thread per listener */ + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->sd != NULL) { + _beginthreadex(NULL, 1000, winnt_accept, + (void *) lr, 0, &tid); + } + } + } +} + + +void child_main(apr_pool_t *pconf) +{ + apr_status_t status; + apr_hash_t *ht; + ap_listen_rec *lr; + HANDLE child_events[2]; + HANDLE *child_handles; + int listener_started = 0; + int threads_created = 0; + int watch_thread; + int time_remains; + int cld; + int tid; + int rv; + int i; + + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + ht = apr_hash_make(pchild); + + /* Initialize the child_events */ + max_requests_per_child_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!max_requests_per_child_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Failed to create a max_requests event.", my_pid); + exit(APEXIT_CHILDINIT); + } + child_events[0] = exit_event; + child_events[1] = max_requests_per_child_event; + + allowed_globals.jobsemaphore = CreateSemaphore(NULL, 0, 1000000, NULL); + apr_thread_mutex_create(&allowed_globals.jobmutex, + APR_THREAD_MUTEX_DEFAULT, pchild); + + /* + * Wait until we have permission to start accepting connections. + * start_mutex is used to ensure that only one child ever + * goes into the listen/accept loop at once. + */ + status = apr_proc_mutex_lock(start_mutex); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, status, ap_server_conf, + "Child %d: Failed to acquire the start_mutex. Process will exit.", my_pid); + exit(APEXIT_CHILDINIT); + } + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Acquired the start mutex.", my_pid); + + /* + * Create the worker thread dispatch IOCompletionPort + * on Windows NT/2000 + */ + if (use_acceptex) { + /* Create the worker thread dispatch IOCP */ + ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, + 0, + 0); /* CONCURRENT ACTIVE THREADS */ + apr_thread_mutex_create(&qlock, APR_THREAD_MUTEX_DEFAULT, pchild); + qwait_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!qwait_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Failed to create a qwait event.", my_pid); + exit(APEXIT_CHILDINIT); + } + } + + /* + * Create the pool of worker threads + */ + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Starting %d worker threads.", my_pid, ap_threads_per_child); + child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child * sizeof(int)); + apr_thread_mutex_create(&child_lock, APR_THREAD_MUTEX_DEFAULT, pchild); + + while (1) { + for (i = 0; i < ap_threads_per_child; i++) { + int *score_idx; + int status = ap_scoreboard_image->servers[0][i].status; + if (status != SERVER_GRACEFUL && status != SERVER_DEAD) { + continue; + } + ap_update_child_status_from_indexes(0, i, SERVER_STARTING, NULL); + child_handles[i] = (HANDLE) _beginthreadex(NULL, 0, worker_main, + (void *) i, 0, &tid); + if (child_handles[i] == 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: _beginthreadex failed. Unable to create all worker threads. " + "Created %d of the %d threads requested with the ThreadsPerChild configuration directive.", + my_pid, threads_created, ap_threads_per_child); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + goto shutdown; + } + threads_created++; + /* Save the score board index in ht keyed to the thread handle. We need this + * when cleaning up threads down below... + */ + apr_thread_mutex_lock(child_lock); + score_idx = apr_pcalloc(pchild, sizeof(int)); + *score_idx = i; + apr_hash_set(ht, &child_handles[i], sizeof(HANDLE), score_idx); + apr_thread_mutex_unlock(child_lock); + } + /* Start the listener only when workers are available */ + if (!listener_started && threads_created) { + create_listener_thread(); + listener_started = 1; + winnt_mpm_state = AP_MPMQ_RUNNING; + } + if (threads_created == ap_threads_per_child) { + break; + } + /* Check to see if the child has been told to exit */ + if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { + break; + } + /* wait for previous generation to clean up an entry in the scoreboard */ + apr_sleep(1 * APR_USEC_PER_SEC); + } + + /* Wait for one of three events: + * exit_event: + * The exit_event is signaled by the parent process to notify + * the child that it is time to exit. + * + * max_requests_per_child_event: + * This event is signaled by the worker threads to indicate that + * the process has handled MaxRequestsPerChild connections. + * + * TIMEOUT: + * To do periodic maintenance on the server (check for thread exits, + * number of completion contexts, etc.) + * + * XXX: thread exits *aren't* being checked. + * + * XXX: other_child - we need the process handles to the other children + * in order to map them to apr_proc_other_child_read (which is not + * named well, it's more like a_p_o_c_died.) + * + * XXX: however - if we get a_p_o_c handle inheritance working, and + * the parent process creates other children and passes the pipes + * to our worker processes, then we have no business doing such + * things in the child_main loop, but should happen in master_main. + */ + while (1) { +#if !APR_HAS_OTHER_CHILD + rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; +#else + rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, 1000); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_TIMEOUT) { + apr_proc_other_child_check(); + } + else +#endif + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: WAIT_FAILED -- shutting down server", my_pid); + break; + } + else if (cld == 0) { + /* Exit event was signaled */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Exit event signaled. Child process is ending.", my_pid); + break; + } + else { + /* MaxRequestsPerChild event set by the worker threads. + * Signal the parent to restart + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Process exiting because it reached " + "MaxRequestsPerChild. Signaling the parent to " + "restart a new child process.", my_pid); + ap_signal_parent(SIGNAL_PARENT_RESTART); + break; + } + } + + /* + * Time to shutdown the child process + */ + + shutdown: + + winnt_mpm_state = AP_MPMQ_STOPPING; + /* Setting is_graceful will cause threads handling keep-alive connections + * to close the connection after handling the current request. + */ + is_graceful = 1; + + /* Close the listening sockets. Note, we must close the listeners + * before closing any accept sockets pending in AcceptEx to prevent + * memory leaks in the kernel. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + apr_socket_close(lr->sd); + } + + /* Shutdown listener threads and pending AcceptEx socksts + * but allow the worker threads to continue consuming from + * the queue of accepted connections. + */ + shutdown_in_progress = 1; + + Sleep(1000); + + /* Tell the worker threads to exit */ + workers_may_exit = 1; + + /* Release the start_mutex to let the new process (in the restart + * scenario) a chance to begin accepting and servicing requests + */ + rv = apr_proc_mutex_unlock(start_mutex); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, rv, ap_server_conf, + "Child %d: Released the start mutex", my_pid); + } + else { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + "Child %d: Failure releasing the start mutex", my_pid); + } + + /* Shutdown the worker threads */ + if (!use_acceptex) { + for (i = 0; i < threads_created; i++) { + add_job(INVALID_SOCKET); + } + } + else { /* Windows NT/2000 */ + /* Post worker threads blocked on the ThreadDispatch IOCompletion port */ + while (g_blocked_threads > 0) { + ap_log_error(APLOG_MARK,APLOG_INFO, APR_SUCCESS, ap_server_conf, + "Child %d: %d threads blocked on the completion port", my_pid, g_blocked_threads); + for (i=g_blocked_threads; i > 0; i--) { + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_SHUTDOWN, NULL); + } + Sleep(1000); + } + /* Empty the accept queue of completion contexts */ + apr_thread_mutex_lock(qlock); + while (qhead) { + CloseHandle(qhead->Overlapped.hEvent); + closesocket(qhead->accept_socket); + qhead = qhead->next; + } + apr_thread_mutex_unlock(qlock); + } + + /* Give busy threads a chance to service their connections, + * (no more than the global server timeout period which + * we track in msec remaining). + */ + watch_thread = 0; + time_remains = (int)(ap_server_conf->timeout / APR_TIME_C(1000)); + + while (threads_created) + { + int nFailsafe = MAXIMUM_WAIT_OBJECTS; + DWORD dwRet; + + /* Every time we roll over to wait on the first group + * of MAXIMUM_WAIT_OBJECTS threads, take a breather, + * and infrequently update the error log. + */ + if (watch_thread >= threads_created) { + if ((time_remains -= 100) < 0) + break; + + /* Every 30 seconds give an update */ + if ((time_remains % 30000) == 0) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, + ap_server_conf, + "Child %d: Waiting %d more seconds " + "for %d worker threads to finish.", + my_pid, time_remains / 1000, threads_created); + } + /* We'll poll from the top, 10 times per second */ + Sleep(100); + watch_thread = 0; + } + + /* Fairness, on each iteration we will pick up with the thread + * after the one we just removed, even if it's a single thread. + * We don't block here. + */ + dwRet = WaitForMultipleObjects(min(threads_created - watch_thread, + MAXIMUM_WAIT_OBJECTS), + child_handles + watch_thread, 0, 0); + + if (dwRet == WAIT_FAILED) { + break; + } + if (dwRet == WAIT_TIMEOUT) { + /* none ready */ + watch_thread += MAXIMUM_WAIT_OBJECTS; + continue; + } + else if (dwRet >= WAIT_ABANDONED_0) { + /* We just got the ownership of the object, which + * should happen at most MAXIMUM_WAIT_OBJECTS times. + * It does NOT mean that the object is signaled. + */ + if ((nFailsafe--) < 1) + break; + } + else { + watch_thread += (dwRet - WAIT_OBJECT_0); + if (watch_thread >= threads_created) + break; + cleanup_thread(child_handles, &threads_created, watch_thread); + } + } + + /* Kill remaining threads off the hard way */ + if (threads_created) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Terminating %d threads that failed to exit.", + my_pid, threads_created); + } + for (i = 0; i < threads_created; i++) { + int *score_idx; + TerminateThread(child_handles[i], 1); + CloseHandle(child_handles[i]); + /* Reset the scoreboard entry for the thread we just whacked */ + score_idx = apr_hash_get(ht, &child_handles[i], sizeof(HANDLE)); + ap_update_child_status_from_indexes(0, *score_idx, SERVER_DEAD, NULL); + } + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: All worker threads have exited.", my_pid); + + CloseHandle(allowed_globals.jobsemaphore); + apr_thread_mutex_destroy(allowed_globals.jobmutex); + apr_thread_mutex_destroy(child_lock); + + if (use_acceptex) { + apr_thread_mutex_destroy(qlock); + CloseHandle(qwait_event); + } + + apr_pool_destroy(pchild); + CloseHandle(exit_event); +} + +#endif /* def WIN32 */ -- cgit 1.2.3-korg