in server/mpm/winnt/child.c [894:1277]
void child_main(apr_pool_t *pconf, DWORD parent_pid)
{
apr_status_t status;
ap_listen_rec *lr;
HANDLE child_events[3];
struct worker_info *workers;
int listener_started = 0;
int threads_created = 0;
int time_remains;
int cld;
DWORD tid;
int rv;
int i;
int num_events;
int graceful_shutdown = 0;
/* Get a sub context for global allocations in this child, so that
* we can have cleanups occur when the child exits.
*/
apr_pool_create(&pchild, pconf);
apr_pool_tag(pchild, "pchild");
ap_run_child_init(pchild, ap_server_conf);
listener_shutdown_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!listener_shutdown_event) {
ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(10035)
"Child: Failed to create a listener_shutdown event.");
exit(APEXIT_CHILDINIT);
}
/* 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, APLOGNO(00350)
"Child: Failed to create a max_requests event.");
exit(APEXIT_CHILDINIT);
}
child_events[0] = exit_event;
child_events[1] = max_requests_per_child_event;
if (parent_pid != my_pid) {
child_events[2] = OpenProcess(SYNCHRONIZE, FALSE, parent_pid);
if (child_events[2] == NULL) {
num_events = 2;
ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(02643)
"Child: Failed to open handle to parent process %ld; "
"will not react to abrupt parent termination", parent_pid);
}
else {
num_events = 3;
}
}
else {
/* presumably -DONE_PROCESS */
child_events[2] = NULL;
num_events = 2;
}
/*
* 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, APLOGNO(00351)
"Child: Failed to acquire the start_mutex. "
"Process will exit.");
exit(APEXIT_CHILDINIT);
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00352)
"Child: Acquired the start mutex.");
/*
* Create the worker thread dispatch IOCompletionPort
*/
ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
NULL, 0, 0);
apr_thread_mutex_create(&ctxpool_lock, APR_THREAD_MUTEX_DEFAULT, pchild);
ctxpool_wait_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ctxpool_wait_event) {
ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(),
ap_server_conf, APLOGNO(00353)
"Child: Failed to create a ctxpool_wait event.");
exit(APEXIT_CHILDINIT);
}
/*
* Create the pool of worker threads
*/
ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00354)
"Child: Starting %d worker threads.", ap_threads_per_child);
workers = apr_pcalloc(pchild, ap_threads_per_child * sizeof(*workers));
apr_thread_mutex_create(&child_lock, APR_THREAD_MUTEX_DEFAULT, pchild);
while (1) {
int from_previous_generation = 0, starting_up = 0;
for (i = 0; i < ap_threads_per_child; i++) {
int status = ap_scoreboard_image->servers[0][i].status;
if (status != SERVER_GRACEFUL && status != SERVER_DEAD) {
if (ap_scoreboard_image->servers[0][i].generation != my_generation) {
++from_previous_generation;
}
else if (status == SERVER_STARTING) {
++starting_up;
}
continue;
}
ap_update_child_status_from_indexes(0, i, SERVER_STARTING, NULL);
workers[i].num = i;
workers[i].handle = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!workers[i].handle) {
rv = apr_get_os_error();
}
else {
apr_threadattr_t *thread_attr = NULL;
apr_threadattr_create(&thread_attr, pchild);
if (ap_thread_stacksize != 0) {
apr_threadattr_stacksize_set(thread_attr,
ap_thread_stacksize);
}
rv = ap_thread_create(&workers[i].thd, thread_attr,
worker_thread, &workers[i], pchild);
}
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00355)
"Child: thread creation failed. Unable to "
"create all worker threads. Created %d of the %d "
"threads requested with the ThreadsPerChild "
"configuration directive.",
threads_created, ap_threads_per_child);
ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
goto shutdown;
}
ap_scoreboard_image->servers[0][i].pid = my_pid;
ap_scoreboard_image->servers[0][i].generation = my_generation;
threads_created++;
}
/* 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
*/
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ap_server_conf,
"Child: %d threads starting up, %d remain from a prior generation",
starting_up, from_previous_generation);
apr_sleep(apr_time_from_sec(1));
}
/* Wait for one of these 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.
*
* parent process exiting
*
* 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(num_events, (HANDLE *)child_events, FALSE, INFINITE);
cld = rv - WAIT_OBJECT_0;
#else
/* THIS IS THE EXPECTED BUILD VARIATION -- APR_HAS_OTHER_CHILD */
rv = WaitForMultipleObjects(num_events, (HANDLE *)child_events, FALSE, 1000);
cld = rv - WAIT_OBJECT_0;
if (rv == WAIT_TIMEOUT) {
apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING);
}
else
#endif
if (rv == WAIT_FAILED) {
/* Something serious is wrong */
ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(),
ap_server_conf, APLOGNO(00356)
"Child: WAIT_FAILED -- shutting down server");
/* check handle validity to identify a possible culprit */
for (i = 0; i < num_events; i++) {
DWORD out_flags;
if (0 == GetHandleInformation(child_events[i], &out_flags)) {
ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(),
ap_server_conf, APLOGNO(02644)
"Child: Event handle #%d (%pp) is invalid",
i, child_events[i]);
}
}
break;
}
else if (cld == 0) {
/* Exit event was signaled */
ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00357)
"Child: Exit event signaled. Child process is "
"ending.");
graceful_shutdown = 1;
break;
}
else if (cld == 2) {
/* The parent is dead. Shutdown the child process. */
ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(02538)
"Child: Parent process exited abruptly. Child process "
"is ending");
break;
}
else {
/* MaxConnectionsPerChild event set by the worker threads.
* Signal the parent to restart
*/
ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00358)
"Child: Process exiting because it reached "
"MaxConnectionsPerChild. Signaling the parent to "
"restart a new child process.");
ap_signal_parent(SIGNAL_PARENT_RESTART);
graceful_shutdown = 1;
break;
}
}
/*
* Time to shutdown the child process
*/
shutdown:
winnt_mpm_state = AP_MPMQ_STOPPING;
/* 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 sockets
* but allow the worker threads to continue consuming the
* already accepted connections.
*/
SetEvent(listener_shutdown_event);
/* Notify anyone interested that this child is stopping.
*/
ap_run_child_stopping(pchild, graceful_shutdown);
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_DEBUG, rv, ap_server_conf, APLOGNO(00359)
"Child: Released the start mutex");
}
else {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00360)
"Child: Failure releasing the start mutex");
}
/* Shutdown the worker threads by posting a number of IOCP_SHUTDOWN
* completion packets equal to the amount of created threads
*/
for (i = 0; i < threads_created; i++) {
PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_SHUTDOWN, NULL);
}
/* Empty the pool of completion contexts */
apr_thread_mutex_lock(ctxpool_lock);
while (ctxpool_head) {
CloseHandle(ctxpool_head->overlapped.hEvent);
closesocket(ctxpool_head->accept_socket);
ctxpool_head = ctxpool_head->next;
}
apr_thread_mutex_unlock(ctxpool_lock);
/* Give busy threads a chance to service their connections
* (no more than the global server timeout period which
* we track in msec remaining).
*/
time_remains = (int)(ap_server_conf->timeout / APR_TIME_C(1000));
while (threads_created)
{
struct worker_info *info = &workers[threads_created - 1];
DWORD dwRet;
if (time_remains < 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, APLOGNO(00362)
"Child: Waiting %d more seconds "
"for %d worker threads to finish.",
time_remains / 1000, threads_created);
}
dwRet = WaitForSingleObject(info->handle, 100);
time_remains -= 100;
if (dwRet == WAIT_TIMEOUT) {
/* Keep waiting */
}
else if (dwRet == WAIT_OBJECT_0) {
apr_status_t thread_rv;
apr_thread_join(&thread_rv, info->thd);
CloseHandle(info->handle);
threads_created--;
}
else {
break;
}
}
if (threads_created) {
ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00363)
"Child: Waiting for %d threads timed out, terminating process.",
threads_created);
for (i = 0; i < threads_created; i++) {
struct worker_info *info = &workers[i];
ap_update_child_status_from_indexes(0, info->num, SERVER_DEAD, NULL);
}
/* We can't wait for any longer, but still have some threads remaining.
*
* The only thing we can do is terminate the process and let the OS
* perform the required cleanup. Terminate with exit code 0, as we do
* not want the parent process to restart the child. Note that we use
* TerminateProcess() instead of ExitProcess(), as the latter function
* causes all DLLs to be unloaded, and it can potentially deadlock if
* a DLL unload handler tries to acquire the same lock that is being
* held by one of the remaining worker threads.
*
* See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682658
*/
TerminateProcess(GetCurrentProcess(), 0);
}
ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00364)
"Child: All worker threads have exited.");
ap_run_child_stopped(pchild, graceful_shutdown);
apr_thread_mutex_destroy(child_lock);
apr_thread_mutex_destroy(ctxpool_lock);
CloseHandle(ctxpool_wait_event);
CloseHandle(ThreadDispatchIOCP);
apr_pool_destroy(pchild);
CloseHandle(exit_event);
if (child_events[2] != NULL) {
CloseHandle(child_events[2]);
}
}