/* $Id$ 
 * 
 * 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. 
 */ 

/*
 * etchthread.c
 * threads, thread pool
 */

#include "apr_network_io.h"
#include "etch_global.h"
#include "etchthread.h"
#include "etchlog.h"

size_t etch_get_threadid(void* threadstruct);
int default_on_threadstart(void* param);
int default_on_threadexit (void* param);
int destroy_thread   (etch_thread*);
int etch_thread_start(etch_thread*);
int etch_thread_stop (etch_thread*);
int destroy_threadparams(etch_thread*);
etchmutex* loglock;
etchmutex  loglockx;
char* ETCHTHRE = "THRE";

#ifdef APR_POOL_DEBUG
APR_DECLARE(apr_size_t) apr_pool_num_bytes(apr_pool_t *p, int recurse);   
#endif


/**
 * etch_apr_threadproc()
 * internal thread proc. this is a wrapper around the user thread proc.
 * @param data expected to point at an etch_threadparams, which contains the
 * thread start amd stop handlers, and the real threadproc and threadproc data.
 */
static void* APR_THREAD_FUNC etch_apr_threadproc(apr_thread_t *thread, void *data)
{    
    etch_threadparams* params = (etch_threadparams*) data;
    etch_thread_callback on_exit;   
    etch_thread* threadobj;
    if(!params) return (void*)(-1);
    on_exit   = params->on_exit;
    threadobj = params->etchobj;

    if (threadobj && threadobj->waiter)  /* wait for signal to start */
    {
        threadobj->waiter->wait (threadobj->waiter);

        /* fyi we can't destroy the waiter here since etchwait_signal  
         * holds etchwait.mutex and will try to release it.  
         * destroy_etchwait (threadobj->waiter);  no!  
         * threadobj->waiter = NULL;  
         */              
    }

    params->threadstate = ETCH_THREADSTATE_STARTED;

    if (params->on_start)    /* call threadproc start hook */
        params->on_start(threadobj);

    if (params->threadproc)  /* run user threadproc */
        params->threadproc(params);  

    params->threadstate = ETCH_THREADSTATE_STOPPED;
	    
    if (on_exit)             /* call threadproc exit hook */
        on_exit(threadobj);    

    return 0;
}


/**
 * new_thread()
 * etch_thread constructor
 */
etch_thread* new_thread (etch_threadproc proc, void* tdata)
{
    etch_threadparams* tp  = NULL;
    apr_pool_t* newsubpool = NULL;
    etch_thread* newthread = NULL;
    /* size_t poolsize = apr_pool_num_bytes(get_etch_aprpool(), FALSE); */  

    if (-1 == apr_pool_create(&newsubpool, get_etch_aprpool())) 
        return NULL;

    newthread = (etch_thread*) new_object
        (sizeof(etch_thread), ETCHTYPEB_THREAD, CLASSID_THREAD);

    newthread->destroy = destroy_thread;
    newthread->clone   = clone_null;
 
    /* allocating an apr memory pool per thread is not ideal, however it was
     * not immediately clear how to free apr memory for an apr_thread_t and
     * for the apr_threadattr_t, apr documentation being nonexistent in that 
     * respect. once we find out, we should change this code to not use a
     * subpool, and change the destructor to free those two data structures.
     */
    newthread->aprdata.mempool = newsubpool;  
    newthread->aprdata.is_dedicated_mempool = TRUE; 

    tp = &newthread->params;
    etch_init_threadparams(tp);
    tp->etchobj  = newthread;
    tp->libdata  = &newthread->aprdata;
    tp->on_start = default_on_threadstart;
    tp->on_exit  = default_on_threadexit;
    tp->etch_thread_id = next_etch_threadid();
    tp->threadproc = proc;
    tp->data = tdata;

    newthread->start = etch_thread_start;
    newthread->stop  = etch_thread_stop; 

    if (0 == etch_createthread(tp)) 
        newthread->waiter = new_wait(newthread->aprdata.mempool);
    else
    {   etch_free(newthread);
        newthread = NULL;
        apr_pool_destroy(newsubpool);
    } 

    return newthread;
} 


/**
 * etch_createthread()
 * to invoke: 1) initalize an etch_apr_threaddata with the mempool; 
 * 2) initialize an etch_threadparams with threadproc plus the etch_apr_threaddata,
 *    plus the thread user data.
 * 2) call with the threaddata, and the userdata.
 */
int etch_createthread(etch_threadparams* params)
{    
    int result = 0;
    apr_pool_t* mempool = params->libdata->mempool;
    if (!params->on_start) params->on_start = default_on_threadstart;
    if (!params->on_exit)  params->on_exit  = default_on_threadexit;

    /* fyi: this sets thread.aprdata.threadattr */
    result = (APR_SUCCESS == apr_threadattr_create
        (&params->libdata->threadattr, mempool))? 0: -1;

    if (result == 0)  /* fyi: this sets thread.aprdata.thread */
        result = (APR_SUCCESS == apr_thread_create
           (&params->libdata->thread, params->libdata->threadattr, 
            etch_apr_threadproc, params, mempool))? 0: -1;  

    return result; 
}


/**
 * destroy_thread()
 * etch_thread destructor
 */
int destroy_thread(etch_thread* threadx)
{
    if (threadx->refcount > 0 && --threadx->refcount > 0) return -1; 

    if (!is_etchobj_static_content(threadx))
    {
        if (threadx->waiter)
            destroy_etchwait(threadx->waiter);

        if (threadx->aprdata.is_dedicated_mempool)
            apr_pool_destroy(threadx->aprdata.mempool);
    }

    return destroy_objectex((objmask*)threadx);   
}


/**
 * destroy_threadparams()
 * free memory for any heap parameters passed to an etch_thread.
 */
int destroy_threadparams (etch_thread* thisx)
{
    etch_threadparams* params = &thisx->params;
    void* userdata = params->data;

    if (params->waitobj) 
        params->waitobj->destroy(params->waitobj);          

    if (params->is_own_data)
        if (params->is_data_etchobject)
           ((objmask*)userdata)->destroy(userdata);
        else etch_free(userdata);

    return 0;
}


/**
 * etch_init_threadparams()
 */
void etch_init_threadparams (etch_threadparams* p)
{
    if (NULL == p) return;
    memset(p, 0, sizeof(etch_threadparams));
    p->signature = ETCH_THREADPARAMS_SIGNATURE;
}


/**
 * etch_thread_start()
 * default thread start method for etch_threads which wait for a start signal
 */
int etch_thread_start (etch_thread* threadx)
{
    int result = -1, threadstate = 0;
    const int thread_id = threadx->params.etch_thread_id;
    etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d starting ...\n", thread_id); 

    if (threadx->waiter)
        result = threadx->waiter->signal(threadx->waiter);

    /* etch_tcpsvr_acceptproc: while(lxr->is_started) was not seeing the started 
     * flag set, because the thread had not received control after the above signal.
     * so this sleep forces it to run, the visual indication of the accept thread 
     * running being the "accepting ..." log message. TODO lose the sleep().
     * NOTE that by the time this thread regains control, its connection may
     * have been closed, or the started thread may possibly even have exited. 
     */
    etch_sleep(30);  /* see comments above */

    /* there is currently no pressing need to log this info, 
     * and since it may indeed be stale, we'll not present it for now. */
    #if(0) 
    threadstate = is_etch_thread(threadx)? 
        threadx->params.threadstate: ETCH_THREADSTATE_DEFUNCT;

    switch(threadstate)
    {   case ETCH_THREADSTATE_STARTED:
             etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d started\n", thread_id); 
             break;
        case ETCH_THREADSTATE_STOPPED:
             etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d stopped\n", thread_id); 
             break;
        case ETCH_THREADSTATE_DEFUNCT:
             etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d was started now destroyed\n", thread_id); 
             break;
        default: etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d state %d\n", thread_id, threadstate);
    }
    #endif

    return result;
}


/**
 * etch_thread_stop()
 * default etch_thread thread stop method.
 * currently never invoked. causes serious grief.
 */
int etch_thread_stop (etch_thread* thread)
{
    apr_status_t tresult = 0;   
    etch_threadparams* p = thread? &thread->params: NULL;
    if (NULL == p) return -1;

    tresult = apr_thread_detach(p->libdata->thread);

    tresult = apr_thread_exit(p->libdata->thread, 0);

    return APR_SUCCESS == tresult? 0: -1;
}


/**
 * etch_threadpool_run_freethread()
 * instantiate and possibly run a free thread.
 */
etch_thread* etch_threadpool_run_freethread (etch_threadpool* pool, 
    etch_threadproc threadproc, void* threaddata)
{
    etch_thread* newthread = NULL;
    if (!pool || pool->is_defunct) return NULL;

    /* create thread in a wait state. it may be started below. */
    if (NULL == (newthread = new_thread (threadproc, threaddata)))
        return NULL;  

    newthread->params.etchpool = pool;
    newthread->params.is_own_data = pool->is_free_data;
    newthread->params.is_data_etchobject = pool->is_data_etchobject;

    arraylist_add(pool->threadlist, newthread);  
    /* an etch_thread object in the threadpool gets freed when a thread exits, 
     * finds its threadpool, and removes itself from that pool's threadlist. 
     */
    etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d created on pool %d\n", 
        newthread->params.etch_thread_id, pool->threadpool_id); 

    if (!pool->is_manual_start) /* start thread unless requested otherwise */
        newthread->start(newthread);  

    return newthread;
}


/**
 * etch_threadpool_run_poolthread()
 * run a thread from a queued pool.
 */
etch_thread* etch_threadpool_run_poolthread (etch_threadpool* pool, 
    etch_threadproc threadproc, void* threaddata)
{
    if (!pool || pool->is_defunct) return NULL;
    return NULL; /* we have not yet implemented or integrated a queued thread pool */
}


/**
 * thread_cancel()
 * cancel a running thread
 */
int etch_thread_cancel(etch_thread* thread)
{
    thread->params.threadstate = ETCH_THREADSTATE_STOPPING;
    etch_join(thread); 
    return 0;       
}


/**
 * etch_join()
 * block until specified thread exits
 */
int etch_join(etch_thread* thread)
{
   int  result = 0;
   etch_threadparams* tp = &thread->params;
   apr_status_t tresult = 0;   /* result returned from dead thread */
   thread->is_joined = TRUE;  /* mark to avoid on_exit destruction */
   result = apr_thread_join (&tresult, tp->libdata->thread); 
   return result;
}


/**
 * etch_thread_join()
 * block until specified thread exits
 */
int etch_thread_join(etch_threadparams* params)
{
   apr_status_t tresult = 0;  /* result returned from dead thread */
   const int result = apr_thread_join(&tresult, params->libdata->thread); 
   return result;
}


/**
 * etch_thread_yield()
 */
void etch_thread_yield()
{
   apr_thread_yield();
}


/**
 * etch_thread_arraylist_comparator()
 * arraylist comparator function to compare a specified thread ID 
 * with the ID of a specified etch_thread
 */
int etch_thread_arraylist_comparator (void* id, void* etchobj)
{
    const int this_id = (int) (size_t) id;
    const int that_id = ((etch_thread*) etchobj)->params.etch_thread_id;
    return this_id < that_id? -1: this_id > that_id? 1: 0;
}


/**
 * threadpool_removeentry()
 * remove a thread from a thread pool, not destroying the etch_thread content.
 * @return the removed etch_thread object.
 */
etch_thread* threadpool_remove_entry (etch_threadpool* pool, const int etch_thread_id)
{
    int result = -1, i = 0;
    etch_thread* outthread = NULL;
    ETCH_ASSERT(is_etch_threadpool(pool));
    if (pool->is_defunct) return NULL;
    pool->pool_lock->acquire(pool->pool_lock);

    i = arraylist_indexof (pool->threadlist, (void*) (size_t) etch_thread_id,   
         0, etch_thread_arraylist_comparator);

    if (i >= 0)  
    {   outthread = arraylist_get (pool->threadlist, i);
        result = arraylist_remove (pool->threadlist, i, FALSE); 
    }

    pool->pool_lock->release(pool->pool_lock);
    return 0 == result? outthread: NULL;
}


/**
 * threadpool_remove()
 * remove a thread from a thread pool, destroying the etch_thread object.
 * this should not be invoked by thread pool destructor, since is_defunct is then true.
 */
int threadpool_remove (etch_threadpool* pool, const int etch_thread_id)
{
    int result = 0;
    ETCH_ASSERT(is_etch_threadpool(pool));
    if (pool->is_defunct) return -1;
    pool->pool_lock->acquire(pool->pool_lock);

    result = arraylist_remove_content (pool->threadlist, 
        (void*) (size_t) etch_thread_id, 0, etch_thread_arraylist_comparator); 

    pool->pool_lock->release(pool->pool_lock);
    return result;
}


/**
 * threadpool_waitfor_all()
 * block until all threads in the pool exit.
 * @param is_cancel if true, signal the thread to exit asap, 
 * otherwise wait for normal thread exit.
 */
int threadpool_waitfor_all (etch_threadpool* pool, const int is_cancel)
{
    etch_iterator iterator;
    etch_arraylist* listcopy = NULL;
    int  threadcount = 0, is_iterable = 0;
    if (NULL == pool) return -1;

    pool->pool_lock->acquire(pool->pool_lock);
    if ((threadcount = pool->count(pool)) > 0 && !pool->is_iterating)
        pool->is_iterating = is_iterable = TRUE;
    pool->pool_lock->release(pool->pool_lock);

    if (threadcount == 0) return 0;  /* no active threads to wait for */
    if (!is_iterable) return -1;    /* another thread is iterating this pool */
    
    /* iterate a copy since canceled threads remove themselves from their pool */
    /* note that iterating the actual pool may however be preferable if the
     * locking is done right, since if a thread ahead of the index exits 
     * during this iteration, it would presumably then not show up here. */
    listcopy = new_arraylist_from(pool->threadlist);
    listcopy->is_readonly = TRUE; /* so destructor will not free copied content */
    set_iterator(&iterator, listcopy, &listcopy->iterable);

    /* note that if pool.is_defunct is true, exiting threads which belong to  
     * that pool will not remove themselves from the pool and self-destruct. 
     * so, if is_defunct is true, threads exiting while we are iterating here 
     * will not be destroyed. the threadpool destructor destroy_threadpool() 
     * sets is_defunct. however if threadpool_waitforall() is invoked elsewhere,
     * care must be taken to ensure that pool threads do not exit while we
     * are iterating them here. */

    while(iterator.has_next(&iterator))  /* wait for each pool thread to exit */
    {
        etch_thread* thisthread = (etch_thread*) iterator.current_value;
        etch_thread* removedthread = NULL;

        if (thisthread) 
        {   const int thread_id = thisthread->params.etch_thread_id;
            char x[40]; sprintf(x, "thread %d in pool %d", thread_id, pool->threadpool_id);
          
            if  (is_cancel)
            {    etchlog(ETCHTHRE, ETCHLOG_DEBUG, "canceling %s ...\n", x); 
                 etch_thread_cancel (thisthread); /* BLOCK here */
            }
            else
            {    etchlog(ETCHTHRE, ETCHLOG_XDEBUG, "joining %s ...\n", x);
                 etch_join (thisthread);          /* BLOCK here */
            }

            etchlog(ETCHTHRE, ETCHLOG_DEBUG, "%s ended\n", x);
            
            if (pool->pooltype == ETCH_THREADPOOLTYPE_FREE)
            {   removedthread = threadpool_remove_entry (pool, thread_id);  
                thisthread->destroy (thisthread);   
            } 
        }
         
        iterator.next(&iterator);
    } 

    pool->is_iterating = FALSE;
    listcopy->destroy(listcopy); /* readonly set above so no content destroyed */
    return 0;
}


/**
 * destroy_threadpool()
 * etch_threadpool destructor.
 * @todo add logic for queued pool, or create separate destructor.
 */
int destroy_threadpool(etch_threadpool* pool)
{
    int can_destroy = TRUE;
    if (NULL == pool) return 0;
    if (pool->refcount > 0 && --pool->refcount > 0) return -1; 

    pool->pool_lock->acquire(pool->pool_lock);
    if  (pool->is_defunct)  /* ensure no race */
         can_destroy = FALSE;
    else pool->is_defunct = TRUE; 
    pool->pool_lock->release(pool->pool_lock);
    if (!can_destroy) return -1;

    threadpool_waitfor_all (pool, TRUE);  /* BLOCK until all threads exited */

    /* destroy the threadlist, destroying the etch_thread content in the process.
     * note that each thread's parameter list was destroyed as the thread exited. 
     * threadlist owns the mutex assigned to it and will destroy threadlist_lock.
     */
    if (!is_etchobj_static_content(pool))
    {
        pool->threadlist->destroy(pool->threadlist);
        pool->pool_lock->destroy(pool->pool_lock);
    }

    return destroy_objectex((objmask*)pool);
} 


/**
 * threadpool_count()
 * return count of threads in list 
 */
int threadpool_count(etch_threadpool* pool)
{
    int count = 0;
    ETCH_ASSERT(pool);
    pool->pool_lock->acquire(pool->pool_lock);
    count = pool->threadlist? pool->threadlist->count: 0;
    pool->pool_lock->release(pool->pool_lock);
    return count;
}


/**
 * threadpool_thread()
 * return thread[i] from specified threadpool.
 * @param i the index.
 * @return the etch_thread*, or null if no thread at that index.
 */
etch_thread* threadpool_thread (etch_threadpool* pool, const int i)
{
    etch_thread* thisthread = NULL;
    if (NULL == pool)  return NULL;

    pool->pool_lock->acquire(pool->pool_lock);

    thisthread = i < pool->count(pool)? pool->threadlist->base[i]: NULL;
  
    pool->pool_lock->release(pool->pool_lock);
     
    return thisthread;
}


/**
 * new_threadpool_list()
 * return an arraylist configured appropriately for a thread pool. 
 */
etch_arraylist* new_threadpool_list(const int initsize, etchmutex* mutex)
{
    etch_arraylist* list = new_arraylist(initsize,0);
    /* ETCHARRAYLIST_CONTENT_OBJECT lets list call destroy() on its content */
    list->content_type = ETCHARRAYLIST_CONTENT_OBJECT;
    list->is_readonly  = TRUE; /* list does not own content */
    list->synclock = mutex;    /* list owns this mutex */
    list->synchook = etchmutex_hookproc;
    return list;
} 
   

/**
 * new_threadpool()
 * etch_threadpool constructor
 * @param pooltype ETCH_THREADPOOLTYPE_FREE (default); ETCH_THREADPOOLTYPE_QUEUED 
 * @param initsize initial number of threads, ignored for ETCH_THREADPOOLTYPE_FREE. 
 */
etch_threadpool* new_threadpool(const unsigned pooltype, const int initsize)
{
    apr_pool_t* mempool;

    etch_threadpool* pool = (etch_threadpool*) new_object
        (sizeof(etch_threadpool), ETCHTYPEB_THREADPOOL, CLASSID_THREADPOOL);

    pool->destroy = destroy_threadpool;
    pool->clone   = clone_null;
    pool->count   = threadpool_count;
    pool->run     = pooltype == ETCH_THREADPOOLTYPE_QUEUED?
                    etch_threadpool_run_poolthread: etch_threadpool_run_freethread;

    mempool = etch_apr_mempool;
    pool->pool_lock = new_mutex(mempool, ETCHMUTEX_NESTED);

    pool->threadlist_lock = new_mutex(mempool, ETCHMUTEX_NESTED);
    pool->threadlist = new_threadpool_list(initsize, pool->threadlist_lock);

    pool->is_free_data = TRUE;
    pool->is_data_etchobject = FALSE;
    pool->threadpool_id = ++etch_threadpool_id_farm;
  
    return pool;
} 


/**
 * etch_glopool_exit()
 * wait on all threads in global pool to exit, destroy global pool.
 */
int etch_glopool_exit()
{
    destroy_threadpool(global_free_threadpool);
    global_free_threadpool = NULL;
    return 0;
}


/**
 * global_pool()
 * return singleton global free thread pool
 */
etch_threadpool* etch_glopool()
{
    if (global_free_threadpool == NULL)
        global_free_threadpool = new_threadpool(ETCH_THREADPOOLTYPE_FREE, 0);
    return global_free_threadpool;
}



/**
 * global_pool()
 * return singleton global free thread pool
 * todo destroy this pool somewhere
 */
etch_threadpool* global_pool()
{
    if (global_free_threadpool == NULL)
        global_free_threadpool = new_threadpool(ETCH_THREADPOOLTYPE_FREE, 0);
    return global_free_threadpool;
}


/**
 * getter for APR thread id
 * seems to be wrong - the ID is the same for each thread
 */
size_t etch_get_threadid (void* threadstruct)
{   
    /* can't address apr_thread_t content so we remap part of apr_thread_t */
    struct x { void* mempool; void* threadid; };
    return threadstruct? (size_t) ((struct x*)threadstruct)->threadid: 0;
}


/**
 * get_etch_aprpool()
 * getter for etch global apr memory pool
 */
apr_pool_t* get_etch_aprpool()
{
    return etch_apr_mempool;
}


/**
 * etch_apr_init()
 * runtime initialize apr
 */
int etch_apr_init()
{
    if (etch_apr_mempool) return 0;
    apr_pool_create(&etch_apr_mempool, NULL);
    loglock = &loglockx;
    return etch_init_static_mutex(loglock, TRUE);
}


/**
 * next_etch_threadid()
 * get a sequential ID we use to key threads
 */
unsigned next_etch_threadid()
{
    do 
    { apr_atomic_inc32 ((volatile apr_uint32_t *) &thread_id_farm);
    } while(thread_id_farm == 0);

    return thread_id_farm;
}


/**
 * default_on_threadstart()
 * default thread start callback.
 * the argument passed to start and exit callbacks is the etch_thread*
 */
int default_on_threadstart(void* param)
{
    int  thread_id = 0;
    etch_thread* threadx = (etch_thread*) param;
    etch_threadparams* p = threadx? &threadx->params: 0;
    if (p) thread_id = p->etch_thread_id;
    ETCH_ASSERT(thread_id);

    etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d enter\n", thread_id); 
    return 0;
}


/**
 * default_on_threadexit()
 * default thread exit callback.
 * the argument passed to start and exit callbacks is the etch_thread*
 */
int default_on_threadexit(void* param)
{    
    etch_thread* thisthread = (etch_thread*) param;
    etch_thread* removedthread = NULL;
    etch_threadpool* pool = NULL;
    etch_threadparams* p;
    int  thread_id = 0;
    ETCH_ASSERT(is_etch_thread(thisthread));
    p = &thisthread->params;
    thread_id = p->etch_thread_id;
    pool = p->etchpool;
   
    /* we remove the thread from its threadpool if a free pool, and unless the
     * pool is being destroyed, and unless the pool is otherwise being iterated
     * elswhere - we can't destroy a thread that another thread has joined, for
     * instance, the joiner will do that once unblocked. and we do not remove a
     * pool entry if we are currently iterating the pool.
     */
    if (pool && !pool->is_defunct) 
    {
        const int is_freethread = (pool->pooltype == ETCH_THREADPOOLTYPE_FREE);
        char x[40]; sprintf(x, "thread %d from pool %d", thread_id, pool->threadpool_id);
        etchlog(ETCHTHRE, ETCHLOG_XDEBUG, "removing %s ...\n", x); 
        pool->pool_lock->acquire(pool->pool_lock);
        /* todo dispose of thread elsewhere when is_iterating is true */

        removedthread = threadpool_remove_entry (pool, thread_id); 

        if (NULL == removedthread) /* occasionally observed while in debugger */
            etchlog(ETCHTHRE, ETCHLOG_WARNING, "%s was previously removed\n", x); 
        else    
        if (removedthread != thisthread)  /* condition never observed to date */
            etchlog(ETCHTHRE, ETCHLOG_ERROR, "%s removed unexpectedly\n", x); 
        else
        /* if either the pool is configured to free threads on exit,
         * or this is a free pool that we are not currently iterating over ... */
        if  (!removedthread->is_joined && is_freethread 
         && (pool->is_free_threads || !pool->is_iterating))
        {    removedthread->destroy (removedthread);   
             etchlog(ETCHTHRE, ETCHLOG_XDEBUG, "%s removed and destroyed\n", x);  
        }
        else 
            etchlog(ETCHTHRE, ETCHLOG_XDEBUG, "%s removed\n", x); 

        pool->pool_lock->release(pool->pool_lock);
    }   
    else 
        destroy_threadparams(thisthread);   

    /* we always destroy the thread's caller-allocated parameter memory, but we
     * do not however destroy the etch_thread wrapper when the thread is not in 
     * a threadpool. memory for non-pool threads is managed by caller.
     */
    etchlog(ETCHTHRE, ETCHLOG_DEBUG, "thread %d exit\n", thread_id); 
    return 0;
}


/**
 * etch_init_static_mutex()
 * etchmutex static initializer.
 * uses etch global apr memory pool.
 * defined here in etchthread to avoid circular include
 */
int etch_init_static_mutex(etchmutex* mx, const int is_nestable)
{
    apr_thread_mutex_t* amx = 0;
   if (NULL == etch_apr_mempool) etch_apr_init();
   if (NULL == etch_apr_mempool) return -1;
 
    mx->destroy = NULL; /* since static or on stack */
    mx->clone   = clone_null;
    mx->acquire = etchmutex_acquire;
    mx->release = etchmutex_release;
    mx->tryacquire = etchmutex_tryacquire;

    apr_thread_mutex_create(&amx, 
        is_nestable? APR_THREAD_MUTEX_NESTED: APR_THREAD_MUTEX_UNNESTED, 
        etch_apr_mempool);

    mx->impl = amx;
    return amx? 0: -1;
}


/**
 * etch_sleep()
 * interface to OS sleep in milliseconds
 */
void etch_sleep(const int ms)
{
    apr_sleep(ms * 1000);
}
