diff options
Diffstat (limited to 'rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c')
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c | 1167 |
1 files changed, 0 insertions, 1167 deletions
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 deleted file mode 100644 index 266d0b3c..00000000 --- a/rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c +++ /dev/null @@ -1,1167 +0,0 @@ -/* 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 <malloc.h> -#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 */ |