void child_main()

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]);
    }
}