static void lock_grab(test_mode_e test_mode)
{
    if (test_mode == TEST_PROC) {
        assert(apr_proc_mutex_lock(proc_mutex) == APR_SUCCESS);
    }
    else {
        assert(apr_global_mutex_lock(global_mutex) == APR_SUCCESS);
    }
}
static int JK_METHOD 
jk2_mutex_proc_lock(jk_env_t *env, jk_mutex_t  *jkMutex)
{
    apr_proc_mutex_t *mutex=(apr_proc_mutex_t *)jkMutex->privateData;
    apr_status_t  st;
    
    st=apr_proc_mutex_lock( mutex );
    
    return st;
}
Beispiel #3
0
static void update_global_snap(global_snapshot *gs, process_stats_t *ps, request_rec *r)
{
	gs->discarded          += apr_atomic_read32(&ps->discarded);
	apr_proc_mutex_lock(ps->mutex);
	gs->rec_count          += ps->count_slot;
	gs->total_mb           += ps->total_kb / 1024.0;
	gs->total_queuetime_ms += ps->total_queuetime / 1000.0;
	gs->total_writetime_ms += ps->total_writetime / 1000.0;
	apr_proc_mutex_unlock(ps->mutex);
}
Beispiel #4
0
void tee_update_saved_file_stats(tee_saved_request *sr)
{
	apr_proc_mutex_lock(process_stats->mutex);
	process_stats->count_process++;
	process_stats->count_slot++;
	process_stats->total_kb += sr->filesize / 1024;
	process_stats->total_queuetime += sr->time_dequeued - sr->time_enqueued;
	if (sr->time_written) {
		process_stats->total_writetime += sr->time_written - sr->time_dequeued;
	}
	apr_proc_mutex_unlock(process_stats->mutex);
}
Beispiel #5
0
static process_snapshot *get_snapshot(apr_pool_t *p, process_stats_t *s)
{
	process_snapshot *ss = apr_pcalloc(p, sizeof(process_snapshot));
	ss->discarded = apr_atomic_read32(&s->discarded);
	ss->status = apr_atomic_read32(&s->status);
	ss->queue_len = apr_atomic_read32(&s->queue_len);
	apr_proc_mutex_lock(s->mutex);
	ss->count_process = s->count_process;
	ss->count_slot = s->count_slot;
	apr_proc_mutex_unlock(s->mutex);
	return ss;
}
Beispiel #6
0
static cache *
cacheCreate (int maxsize, int timeout, unsigned long hash, request_data *rd)	/*{{{ */
{
  // create pool for cache to live in
  apr_status_t s;
  apr_pool_t *p;
  s = apr_pool_create (&p, (apr_pool_t *) NULL);
  if (s != APR_SUCCESS) 
  {
    ap_log_error(APLOG_MARK,LOG_WARNING,0,rd->server,"cacheCreate: could not create memory pool");
    raise_exn ((int) &exn_OVERFLOW);
  }
  cache *c = (cache *) apr_palloc (p, sizeof (cache));
  if (c == NULL)
  {
    ap_log_error(APLOG_MARK,LOG_WARNING,0,rd->server,"cacheCreate: could not allocate memory from pool");
    raise_exn ((int) &exn_OVERFLOW);
  }
  ap_log_error(APLOG_MARK,LOG_DEBUG,0,rd->server,"cacheCreate: 0x%x", (unsigned int) c);
  c->pool = p;
  c->hashofname = hash;
  apr_proc_mutex_lock(rd->ctx->cachelock.plock);
  unsigned long cachehash = hash % rd->ctx->cachelock.shmsize;
  c->version = rd->ctx->cachelock.version[cachehash];
  apr_proc_mutex_unlock(rd->ctx->cachelock.plock);

  // setup locks
  apr_thread_rwlock_create (&(c->rwlock), c->pool);
  apr_thread_mutex_create (&(c->mutex), APR_THREAD_MUTEX_DEFAULT, c->pool);

  // setup linked list
  c->sentinel = (entry *) apr_palloc (c->pool, sizeof (entry));
  c->sentinel->up = c->sentinel;
  c->sentinel->down = c->sentinel;
  c->sentinel->key = NULL;
//  c->sentinel->key.hash = 0;
  c->sentinel->data = NULL;
  c->sentinel->size = 0;

  // setup hashtable & binary heap
  c->htable = (entrytable_hashtable_t *) apr_palloc (c->pool, sizeof (entrytable_hashtable_t) + sizeof(cacheheap_binaryheap_t));
  c->heap = (cacheheap_binaryheap_t *) (c->htable + 1);
  entrytable_init (c->htable);
  cacheheap_heapinit(c->heap);

  // calculate size
  c->size = c->htable->hashTableSize * sizeof (entrytable_hashelement_t);
  c->maxsize = maxsize;

  // set timeout scheme
  c->timeout = timeout;
  return c;
}				/*}}} */
Beispiel #7
0
static void accept_mutex_on(void)
{
    apr_status_t rv = apr_proc_mutex_lock(accept_mutex);
    if (rv != APR_SUCCESS) {
        const char *msg = "couldn't grab the accept mutex";

        if (ap_my_generation != 
            ap_scoreboard_image->global->running_generation) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, NULL, msg);
            clean_child_exit(0);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_EMERG, rv, NULL, msg);
            exit(APEXIT_CHILDFATAL);
        }
    }
}
Beispiel #8
0
int
chxj_cookie_lock(request_rec *r)
{
  mod_chxj_config *dconf;
  apr_status_t rv;
  int done_proc = 0;

  DBG(r, "start chxj_cookie_lock()");
  if ((rv = apr_proc_mutex_lock(global_cookie_mutex)) != APR_SUCCESS) {
    char errstr[255];
    ERR(r, "%s:%d apr_proc_mutex_lock failure.(%d:%s)", APLOG_MARK, rv, apr_strerror(rv, errstr, 255));
    return 0;
  }
  dconf = chxj_get_module_config(r->per_dir_config, &chxj_module);
#if defined(USE_MYSQL_COOKIE)
  if (IS_COOKIE_STORE_MYSQL(dconf->cookie_store_type)) {
    if (! chxj_cookie_lock_mysql(r, dconf)) {
      ERR(r, "%s:%d end chxj_cookie_lock(): failed: chxj_cookie_lock_mysql()", APLOG_MARK);
      return 0;
    }
    done_proc = 1;
  } 
#endif
#if defined(USE_MEMCACHE_COOKIE)
  if (IS_COOKIE_STORE_MEMCACHE(dconf->cookie_store_type)) {
    if (! chxj_cookie_lock_memcache(r, dconf)) {
      ERR(r, "%s:%d end chxj_cookie_lock(): failed: chxj_cookie_lock_memcache()", APLOG_MARK);
      return 0;
    }
    done_proc = 1;
  } 
#endif
  if (!done_proc) {
    if (! chxj_cookie_lock_dbm(r, dconf)) {
      ERR(r, "%s:%d end chxj_cookie_lock(): failed: chxj_cookie_lock_dbm()", APLOG_MARK);
      return 0;
    }
  }
  DBG(r, "end   chxj_cookie_lock()");
  return 1;
}
Beispiel #9
0
static void make_child(CuTest *tc, apr_proc_t **proc, apr_pool_t *p)
{
    apr_status_t rv;

    *proc = apr_pcalloc(p, sizeof(**proc));

    /* slight delay to allow things to settle */
    apr_sleep (1);

    rv = apr_proc_fork(*proc, p);
    if (rv == APR_INCHILD) {
        int i = 0;
        /* The parent process has setup all processes to call apr_terminate
         * at exit.  But, that means that all processes must also call
         * apr_initialize at startup.  You cannot have an unequal number
         * of apr_terminate and apr_initialize calls.  If you do, bad things
         * will happen.  In this case, the bad thing is that if the mutex
         * is a semaphore, it will be destroyed before all of the processes
         * die.  That means that the test will most likely fail.
         */
        apr_initialize();

        if (apr_proc_mutex_child_init(&proc_lock, NULL, p))
            exit(1);

        do {
            if (apr_proc_mutex_lock(proc_lock))
                exit(1);
            i++;
            *x = increment(*x);
            if (apr_proc_mutex_unlock(proc_lock))
                exit(1);
        } while (i < MAX_ITER);
        exit(0);
    } 

    CuAssert(tc, "fork failed", rv == APR_INPARENT);
}
Beispiel #10
0
int
apsml_cacheFlush (cache * c, request_data *rd, int global)	/*{{{ */
{
  apr_thread_rwlock_wrlock (c->rwlock);
  if (global)
  {
    apr_proc_mutex_lock(rd->ctx->cachelock.plock);
    unsigned long cachehash = c->hashofname % rd->ctx->cachelock.shmsize;
    rd->ctx->cachelock.version[cachehash]++;
    apr_proc_mutex_unlock(rd->ctx->cachelock.plock);
  }
  while (c->sentinel->down != c->sentinel)
  {
    listremoveitem (c, c->sentinel->down, rd);
  }
  if (entrytable_reinit (c->htable) == hash_OUTOFMEM)
  {
    apr_thread_rwlock_unlock (c->rwlock);
    return 0;
  }
  c->size = c->htable->hashTableSize * sizeof (entrytable_hashelement_t);
  apr_thread_rwlock_unlock (c->rwlock);
  return 1;
}				/*}}} */
Beispiel #11
0
// ML : cache * string -> string_ptr
String
apsml_cacheGet (Region rAddr, cache *c, String key1, request_data *rd)	/*{{{ */
{
//  ap_log_error(APLOG_MARK, LOG_DEBUG, 0, rd->server, "apsml_cacheGet 1");
//  ppCache (c, rd);

//  keyNhash kn;
  char *key = &(key1->data);
//  kn.hash = charhashfunction (kn.key);
//  ap_log_error (APLOG_MARK, LOG_NOTICE, 0, rd->server,
//		"apsml_cacheGet: key == %s, hash: %i", kn.key, kn.hash);
  int too_old = 0;
  apr_thread_rwlock_rdlock (c->rwlock);
  apr_proc_mutex_lock(rd->ctx->cachelock.plock);
  unsigned long cachehash = c->hashofname % rd->ctx->cachelock.shmsize;
  unsigned long cacheversion = rd->ctx->cachelock.version[cachehash];
  apr_proc_mutex_unlock(rd->ctx->cachelock.plock);

//  ap_log_error(APLOG_MARK, LOG_DEBUG, 0, rd->server, 
//          "apsml_cacheGet global version: %d, local version %d", cacheversion, c->version);

  if (cacheversion != c->version)
  {
    apr_thread_rwlock_unlock (c->rwlock);
    apsml_cacheFlush(c, rd, 0);
    c->version = cacheversion;
    return (String) NULL;
  }
  entry *entry;
//  void **entry1 = (void **) &entry;
  if (entrytable_find (c->htable, key, &entry) == hash_DNE)
  {
    entry = NULL;
//    ap_log_error (APLOG_MARK, LOG_NOTICE, 0, rd->server,
//	   "apsml_cacheGet: No such thing");
  }
  if (entry)
  {
    // we found an entry 
    // which should be put on top of the list
    // We require locking on the list
    // If time is too old then drop the entry

    time_t ct = time (NULL);
    apr_thread_mutex_lock (c->mutex);

    LINKEDLIST_REMOVE (entry);
    //time_t t = ct < entry->time ? 0 : ct - entry->time;
    if (entry->timeout)
    {
      if (ct > entry->time)
	    {			// entry too old
	      too_old = 1;
	     /* ap_log_error (APLOG_MARK, LOG_NOTICE, 0, rd->server,
			    "apsml_cacheGet: Entry too old, ct == %ld, entry->time == %ld",
			    ct, entry->time); */
	      LINKEDLIST_INSERTOVER (c->sentinel, entry);
	    }
      else
	    {
	      // keep entry fresh
	      LINKEDLIST_INSERTUNDER (c->sentinel, entry);
//	      ap_log_error (APLOG_MARK, LOG_NOTICE, 0, rd->server,
//			    "apsml_cacheGet: Entry fine ct == %i, entry->time == %i",
//			    ct, entry->time);
        cacheheap_heapchangekey(c->heap, entry->heappos, MAX (ct + entry->timeout, entry->time));
	      //entry->time = MAX (ct + entry->timeout, entry->time);
	    }
	  }
    else
    {
	    LINKEDLIST_INSERTUNDER (c->sentinel, entry);
    }
//    ap_log_rerror(APLOG_MARK, LOG_NOTICE, 0, rd->request, "apsml_cacheGetFound: etime: %d, rtime: %d, too_old: %i key: %s, value %d, valuedata: %s", entry->time, time(NULL), too_old, key, entry, entry->data);
     apr_thread_mutex_unlock (c->mutex);
  }
  String s;
  if (too_old == 0 && entry)
  {
    s = convertStringToML (rAddr, entry->data);
  }
  else
  {
    s = (String) NULL;
  }
  apr_thread_rwlock_unlock (c->rwlock);
//    ap_log_rerror(APLOG_MARK, LOG_NOTICE, 0, rd->request, "apsml_cacheGet: Returning");
  return s;
}				/*}}} */
Beispiel #12
0
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;
    unsigned 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 %lu: 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 %lu: 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 %lu: 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 %lu: 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 %lu: Starting %d worker threads.", my_pid, ap_threads_per_child);
    child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child * sizeof(HANDLE));
    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, (unsigned)ap_thread_stacksize,
                                                       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 %lu: _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_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,
                         "Child %lu: 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 %lu: 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 %lu: 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 %lu: Released the start mutex", my_pid);
    }
    else {
        ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf,
                     "Child %lu: 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 %lu: %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 %lu: 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 %lu: 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));
        if (score_idx) {
            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 %lu: 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);
}
void cse_proc_lock() {
  apr_proc_mutex_lock(g_proc_lock);
}