child.c revision bd6f55fcc632c166165842eb23dcc1105ddde364
/* 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
*
*
* 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
#include "apr.h"
#include <process.h>
#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 "http_vhost.h" /* for ap_update_vhost_given_ip */
#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"
#include "apr_buckets.h"
#include "scoreboard.h"
#ifdef __MINGW32__
#include <mswsock.h>
#endif
/*
* The Windows MPM uses a queue of completion contexts that it passes
* between the accept threads and the worker threads. Declare the
* functions to access the queue and the structures passed on the
* queue in the header file to enable modules to access them
* if necessary. The queue resides in the MPM.
*/
#ifdef CONTAINING_RECORD
#endif
(char *)(address) - \
#if APR_HAVE_IPV6
#else
#endif
/* Queue for managing the passing of winnt_conn_ctx_t between
* the accept and worker threads.
*/
typedef struct winnt_conn_ctx_t_s {
struct winnt_conn_ctx_t_s *next;
int sa_server_len;
int sa_client_len;
#if APR_HAVE_IPV6
short socket_family;
#endif
typedef enum {
IOCP_SHUTDOWN = 4
} io_state_e;
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 apr_uint32_t num_completion_contexts = 0;
static apr_uint32_t max_num_completion_contexts = 0;
{
/* 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) {
if (qtail) {
} else {
}
}
}
{
*timeout = 0;
while (1) {
/* Grab a context off the queue */
if (qhead) {
if (!qhead)
} else {
}
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.
*/
/* All workers are busy, need to wait for one */
static int reported = 0;
if (!reported) {
"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.
*/
if (rv == WAIT_OBJECT_0)
continue;
else {
if (rv == WAIT_TIMEOUT) {
/* somewhat-normal condition where threads are busy */
"mpm_get_completion_context: Failed to get a "
"free context within 1 second");
*timeout = 1;
}
else {
/* should be the unexpected, generic WAIT_FAILED */
"mpm_get_completion_context: "
"WaitForSingleObject failed to get free context");
}
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...
*/
sizeof(winnt_conn_ctx_t));
/* Hopefully this is a temporary condition ... */
"mpm_get_completion_context: "
"CreateEvent failed.");
return NULL;
}
/* Create the transaction pool */
if (rv != APR_SUCCESS) {
"mpm_get_completion_context: Failed "
"to create the transaction pool.");
return NULL;
}
break;
}
} else {
/* Got a context from the queue */
break;
}
}
return context;
}
/* Windows NT/2000 specific code...
* 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 10
{
const char *accf_name;
int rv;
int accf;
int err_count = 0;
#if APR_HAVE_IPV6
#endif
accf = 2;
accf = 1;
accf = 0;
else {
accf = 0;
accf_name = "none";
"winnt_accept: unrecognized AcceptFilter '%s', "
"only 'data', 'connect' or 'none' are valid. "
"Using 'none' instead", accf_name);
}
#if APR_HAVE_IPV6
"winnt_accept: getsockname error on listening socket, "
"is IPv6 available?");
return 1;
}
#endif
if (accf > 0) /* 'data' or 'connect' */
{
/* first, high priority event is an already accepted connection */
}
else /* accf == 0, 'none' */
{
reinit: /* target of data or connect upon too many AcceptEx failures */
/* last, low priority event is a not yet accepted connection */
events[0] = exit_event;
/* The event needs to be removed from the accepted socket,
* if not removed from the listen socket prior to accept(),
*/
if (rv) {
"WSAEventSelect() failed.");
return 1;
}
}
"Child: Accept thread listening on %s:%d using AcceptFilter %s",
while (!shutdown_in_progress) {
if (!context) {
int timeout;
if (!context) {
if (!timeout) {
/* Hopefully a temporary condition in the provider? */
++err_count;
if (err_count > MAX_ACCEPTEX_ERR_COUNT) {
"winnt_accept: Too many failures grabbing a "
"connection ctx. Aborting.");
break;
}
}
Sleep(100);
continue;
}
}
if (accf > 0) /* Either 'connect' or 'data' */
{
char *buf;
/* Create and initialize the accept socket */
#if APR_HAVE_IPV6
}
}
#else
#endif
"winnt_accept: Failed to allocate an accept socket. "
"Temporary resource constraint? Try again.");
Sleep(100);
continue;
}
}
else /* (accf == 1) 'connect' */ {
len = 0;
}
/* AcceptEx on the completion context. The completion context will be
* signaled when a connection is accepted.
*/
&context->overlapped)) {
rv = apr_get_netos_error();
/* We can get here when:
* 1) the client disconnects early
* 2) handshake was incomplete
*/
if (accf == 2)
continue;
}
/* We can get here when:
* 1) TransmitFile does not properly recycle the accept socket (typically
* because the client disconnected)
* 2) there is VPN or Firewall software installed with
* buggy WSAAccept or WSADuplicateSocket implementation
* 3) the dynamic address / adapter has changed
* Give five chances, then fall back on AcceptFilter 'none'
*/
if (accf == 2)
++err_count;
if (err_count > MAX_ACCEPTEX_ERR_COUNT) {
"Child: Encountered too many AcceptEx "
"faults accepting client connections. "
"Possible causes: dynamic address renewal, "
"or incompatible VPN or firewall software. ");
"winnt_mpm: falling back to "
"'AcceptFilter none'.");
err_count = 0;
accf = 0;
}
continue;
}
if (accf == 2)
++err_count;
if (err_count > MAX_ACCEPTEX_ERR_COUNT) {
"Child: Encountered too many AcceptEx "
"faults accepting client connections.");
"winnt_mpm: falling back to "
"'AcceptFilter none'.");
err_count = 0;
accf = 0;
goto reinit;
}
continue;
}
err_count = 0;
do {
} while (rv == WAIT_IO_COMPLETION);
if (rv == WAIT_OBJECT_0) {
"winnt_accept: Asynchronous AcceptEx failed.");
}
}
else {
/* exit_event triggered or event handle was closed */
if (accf == 2)
break;
}
if (accf == 2)
continue;
}
}
err_count = 0;
/* Potential optimization; consider handing off to the worker */
/* Inherit the listen socket settings. Required for
* shutdown() to work
*/
SO_UPDATE_ACCEPT_CONTEXT, (char *)&nlsd,
sizeof(nlsd))) {
"setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed.");
/* Not a failure condition. Keep running. */
}
/* Get the local & remote address
* TODO; error check
*/
/* For 'data', craft a bucket for our data result
* and pass to worker_main as context->overlapped.Pointer
*/
{
apr_bucket *b;
/* Adjust the bucket to refer to the actual bytes read */
}
else
}
else /* (accf = 0) e.g. 'none' */
{
/* There is no socket reuse without AcceptEx() */
/* This could be a persistent event per-listener rather than
* per-accept. However, the event needs to be removed from
* the target socket if not removed from the listen socket
* prior to accept(), or the event select is inherited.
* and must be removed from the accepted socket.
*/
do {
} while (rv == WAIT_IO_COMPLETION);
/* not FD_ACCEPT;
* exit_event triggered or event handle was closed
*/
break;
}
+ context->sa_server_len);
&context->sa_server_len);
rv = apr_get_netos_error();
"accept() failed, retrying.");
continue;
}
/* A more serious error than 'retry', log it */
"accept() failed.");
/* Hopefully a temporary condition in the provider? */
Sleep(100);
++err_count;
if (err_count > MAX_ACCEPTEX_ERR_COUNT) {
"Child: Encountered too many accept() "
"resource faults, aborting.");
break;
}
continue;
}
break;
}
/* Per MSDN, cancel the inherited association of this socket
* to the WSAEventSelect API, and restore the state corresponding
* to apr_os_sock_make's default assumptions (really, a flaw within
* os_sock_make and os_sock_put that it does not query).
*/
err_count = 0;
"getsockname failed");
continue;
}
"getpeername failed");
}
}
/* Restore the state corresponding to apr_os_sock_make's default
* assumption of timeout -1 (really, a flaw of os_sock_make and
* os_sock_put that it does not query to determine ->timeout).
* XXX: Upon a fix to APR, these three statements should disappear.
*/
/* When a connection is received, send an io completion notification
* to the ThreadDispatchIOCP.
*/
&context->overlapped);
}
if (!accf)
if (!shutdown_in_progress) {
/* Yow, hit an irrecoverable error! Tell the child to die. */
}
"Child: Accept thread exiting.");
return 0;
}
{
int rc;
#ifdef _WIN64
#else
#endif
while (1) {
if (workers_may_exit) {
return NULL;
}
if (!rc) {
rc = apr_get_os_error();
"Child: GetQueuedComplationStatus returned %d",
rc);
continue;
}
switch (CompKey) {
case IOCP_CONNECTION_ACCEPTED:
break;
case IOCP_SHUTDOWN:
return NULL;
default:
return NULL;
}
break;
}
return context;
}
{
apr_bucket *e;
return AP_DECLINED;
/* seed the brigade with AcceptEx read heap bucket */
/* also seed the brigade with the client socket. */
return APR_SUCCESS;
}
/*
* worker_main()
* Main entry point for the worker threads. Worker threads block in
* win*_get_connection() awaiting a connection to service.
*/
{
static int requests_this_child = 0;
int thread_num = (int)thread_num_val;
apr_bucket *e;
int rc;
conn_rec *c;
while (1) {
/* Grab a connection off the network */
if (!context) {
/* Time for the thread to exit */
break;
}
/* Have we hit MaxConnectionsPerChild connections? */
if (ap_max_requests_per_child) {
}
}
if (!c)
{
/* ap_run_create_connection closes the socket on failure */
if (e)
apr_bucket_free(e);
continue;
}
c->current_thread = thd;
/* follow ap_process_connection(c, context->sock) logic
* as it left us no chance to reinject our first data bucket.
*/
c->aborted = 1;
}
if (e && c->aborted)
{
apr_bucket_free(e);
}
else
{
}
if (!c->aborted)
{
&disconnected);
if (!disconnected) {
}
}
}
(request_rec *) NULL);
return 0;
}
int thread_to_clean)
{
int i;
(*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(void)
{
unsigned tid;
int num_listeners = 0;
/* Start an accept thread per listener
* XXX: Why would we have a NULL sd in our listeners?
*/
/* 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.
*/
}
/* Now start a thread per listener */
/* A smaller stack is sufficient.
* To convert to CreateThread, the returned handle cannot be
*/
}
}
}
{
apr_hash_t *ht;
int listener_started = 0;
int threads_created = 0;
int watch_thread;
int time_remains;
int cld;
int rv;
int i;
/* Initialize the child_events */
if (!max_requests_per_child_event) {
"Child: Failed to create a max_requests event.");
}
child_events[0] = exit_event;
/*
* Wait until we have permission to start accepting connections.
* start_mutex is used to ensure that only one child ever
*/
if (status != APR_SUCCESS) {
"Child: Failed to acquire the start_mutex. "
"Process will exit.");
}
"Child: Acquired the start mutex.");
/*
* Create the worker thread dispatch IOCompletionPort
*/
/* Create the worker thread dispatch IOCP */
NULL, 0, 0);
if (!qwait_event) {
"Child: Failed to create a qwait event.");
}
/*
* Create the pool of worker threads
*/
"Child: Starting %d worker threads.", ap_threads_per_child);
* sizeof(HANDLE));
while (1) {
for (i = 0; i < ap_threads_per_child; i++) {
int *score_idx;
continue;
}
worker_main, (void *) i,
stack_res_flag, &tid);
if (child_handles[i] == 0) {
"Child: CreateThread failed. Unable to "
"create all worker threads. Created %d of the %d "
"threads requested with the ThreadsPerChild "
"configuration directive.",
goto shutdown;
}
/* Save the score board index in ht keyed to the thread handle.
* We need this when cleaning up threads down below...
*/
*score_idx = i;
}
/* Start the listener only when workers are available */
if (!listener_started && threads_created) {
listener_started = 1;
}
if (threads_created == ap_threads_per_child) {
break;
}
/* Check to see if the child has been told to exit */
break;
}
/* wait for previous generation to clean up an entry in the scoreboard
*/
}
/* 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 MaxConnectionsPerChild 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
#else
if (rv == WAIT_TIMEOUT) {
}
else
#endif
if (rv == WAIT_FAILED) {
/* Something serious is wrong */
"Child: WAIT_FAILED -- shutting down server");
break;
}
else if (cld == 0) {
/* Exit event was signaled */
"Child: Exit event signaled. Child process is "
"ending.");
break;
}
else {
/* MaxConnectionsPerChild event set by the worker threads.
* Signal the parent to restart
*/
"Child: Process exiting because it reached "
"MaxConnectionsPerChild. Signaling the parent to "
"restart a new child process.");
break;
}
}
/*
* Time to shutdown the child process
*/
/* 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.
*/
}
/* Shutdown listener threads and pending AcceptEx sockets
* 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
*/
if (rv == APR_SUCCESS) {
"Child: Released the start mutex");
}
else {
"Child: Failure releasing the start mutex");
}
/* Shutdown the worker threads
* Post worker threads blocked on the ThreadDispatch IOCompletion port
*/
while (g_blocked_threads > 0) {
"Child: %d threads blocked on the completion port",
for (i=g_blocked_threads; i > 0; i--) {
}
Sleep(1000);
}
/* Empty the accept queue of completion contexts */
while (qhead) {
}
/* 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;
while (threads_created)
{
int nFailsafe = MAXIMUM_WAIT_OBJECTS;
/* 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) {
"Child: Waiting %d more seconds "
"for %d worker threads to finish.",
}
/* 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.
*/
child_handles + watch_thread, 0, 0);
if (dwRet == WAIT_FAILED) {
break;
}
if (dwRet == WAIT_TIMEOUT) {
/* none ready */
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 {
if (watch_thread >= threads_created)
break;
}
}
/* Kill remaining threads off the hard way */
if (threads_created) {
"Child: Terminating %d threads that failed to exit.",
}
for (i = 0; i < threads_created; i++) {
int *score_idx;
CloseHandle(child_handles[i]);
/* Reset the scoreboard entry for the thread we just whacked */
if (score_idx) {
}
}
"Child: All worker threads have exited.");
}
#endif /* def WIN32 */