Logo Search packages:      
Sourcecode: libupnp4 version File versions  Download package

ThreadPool.c

Go to the documentation of this file.
/*******************************************************************************
 *
 * Copyright (c) 2000-2003 Intel Corporation 
 * All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 *
 * - Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer. 
 * - Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution. 
 * - Neither name of Intel Corporation nor the names of its contributors 
 * may be used to endorse or promote products derived from this software 
 * without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************/


/*!
 * \file
 */


#include "ThreadPool.h"


#include "FreeList.h"


#include <assert.h>
#include <stdlib.h>
#include <stdio.h>


/****************************************************************************
 * Function: DiffMillis
 *
 *  Description:
 *      Returns the difference in milliseconds between two
 *      timeval structures.
 *      Internal Only.
 *  Parameters:
 *      struct timeval *time1,
 *      struct timeval *time2,
 *  Returns:
 *       the difference in milliseconds, time1-time2.
 *****************************************************************************/
static unsigned long DiffMillis( struct timeval *time1, struct timeval *time2 )
{
      double temp = 0;

      assert( time1 != NULL );
      assert( time2 != NULL );

      temp = time1->tv_sec - time2->tv_sec;
      /* convert to milliseconds */
      temp *= 1000;

      /* convert microseconds to milliseconds and add to temp */
      /* implicit flooring of unsigned long data type */
      temp += (time1->tv_usec - time2->tv_usec) / 1000;

      return temp;
}

#ifdef STATS
/****************************************************************************
 * Function: StatsInit
 *
 *  Description:
 *      Initializes the statistics structure.
 *      Internal Only.
 *  Parameters:
 *      ThreadPoolStats *stats must be valid non null stats structure
 *****************************************************************************/
static void StatsInit( ThreadPoolStats *stats )
{
      assert( stats != NULL );

      stats->totalIdleTime = 0;
      stats->totalJobsHQ = 0;
      stats->totalJobsLQ = 0;
      stats->totalJobsMQ = 0;
      stats->totalTimeHQ = 0;
      stats->totalTimeMQ = 0;
      stats->totalTimeLQ = 0;
      stats->totalWorkTime = 0;
      stats->totalIdleTime = 0;
      stats->avgWaitHQ = 0;
      stats->avgWaitMQ = 0;
      stats->avgWaitLQ = 0;
      stats->workerThreads = 0;
      stats->idleThreads = 0;
      stats->persistentThreads = 0;
      stats->maxThreads = 0; stats->totalThreads = 0;
}

static void StatsAccountLQ( ThreadPool *tp, unsigned long diffTime )
{
      tp->stats.totalJobsLQ++;
      tp->stats.totalTimeLQ += diffTime;
}

static void StatsAccountMQ( ThreadPool *tp, unsigned long diffTime )
{
      tp->stats.totalJobsMQ++;
      tp->stats.totalTimeMQ += diffTime;
}

static void StatsAccountHQ( ThreadPool *tp, unsigned long diffTime )
{
      tp->stats.totalJobsHQ++;
      tp->stats.totalTimeHQ += diffTime;
}

/****************************************************************************
 * Function: CalcWaitTime
 *
 *  Description:
 *      Calculates the time the job has been waiting at the specified
 *      priority. Adds to the totalTime and totalJobs kept in the
 *      thread pool statistics structure.
 *      Internal Only.
 *
 *  Parameters:
 *      ThreadPool *tp
 *      ThreadPriority p
 *      ThreadPoolJob *job
 *****************************************************************************/
static void CalcWaitTime( ThreadPool *tp, ThreadPriority p, ThreadPoolJob *job )
{
      struct timeval now;
      unsigned long diff;

      assert( tp != NULL );
      assert( job != NULL );

      gettimeofday( &now, NULL );
      diff = DiffMillis( &now, &job->requestTime );
      switch ( p ) {
      case LOW_PRIORITY:
            StatsAccountLQ( tp, diff );
            break;
      case MED_PRIORITY:
            StatsAccountMQ( tp, diff );
            break;
      case HIGH_PRIORITY:
            StatsAccountHQ( tp, diff );
            break;
      default:
            assert( 0 );
      }
}

static time_t StatsTime( time_t *t )
{
      struct timeval tv;

      gettimeofday( &tv, NULL );
      if (t) {
            *t = tv.tv_sec;
      }

      return tv.tv_sec;
}
#else /* STATS */
static UPNP_INLINE void StatsInit( ThreadPoolStats *stats ) {}
static UPNP_INLINE void StatsAccountLQ( ThreadPool *tp, unsigned long diffTime ) {}
static UPNP_INLINE void StatsAccountMQ( ThreadPool *tp, unsigned long diffTime ) {}
static UPNP_INLINE void StatsAccountHQ( ThreadPool *tp, unsigned long diffTime ) {}
static UPNP_INLINE void CalcWaitTime( ThreadPool *tp, ThreadPriority p, ThreadPoolJob *job ) {}
static UPNP_INLINE time_t StatsTime( time_t *t ) { return 0; }
#endif /* STATS */

/****************************************************************************
 * Function: CmpThreadPoolJob
 *
 *  Description:
 *      Compares thread pool jobs.
 *  Parameters:
 *      void * - job A
 *      void * - job B
 *****************************************************************************/
static int CmpThreadPoolJob( void *jobA, void *jobB )
{
      ThreadPoolJob *a = ( ThreadPoolJob *) jobA;
      ThreadPoolJob *b = ( ThreadPoolJob *) jobB;

      assert( jobA != NULL );
      assert( jobB != NULL );

      return ( a->jobId == b->jobId );
}

/****************************************************************************
 * Function: FreeThreadPoolJob
 *
 *  Description:
 *      Deallocates a dynamically allocated ThreadPoolJob.
 *  Parameters:
 *      ThreadPoolJob *tpj - must be allocated with CreateThreadPoolJob
 *****************************************************************************/
static void FreeThreadPoolJob( ThreadPool *tp, ThreadPoolJob *tpj )
{
      assert( tp != NULL );

      FreeListFree( &tp->jobFreeList, tpj );
}

/****************************************************************************
 * Function: SetPolicyType
 *
 *  Description:
 *      Sets the scheduling policy of the current process.
 *      Internal only.
 *  Parameters:
 *      PolocyType in
 *  Returns:
 *      0 on success, nonzero on failure
 *      Returns result of GetLastError() on failure.
 *
 *****************************************************************************/
static int SetPolicyType( PolicyType in )
{
#ifdef __CYGWIN__
      /* TODO not currently working... */
      return 0;
#elif defined(__OSX__) || defined(__APPLE__)
      setpriority( PRIO_PROCESS, 0, 0 );
      return 0;
#elif defined(WIN32)
      return sched_setscheduler( 0, in );
#elif defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0
      struct sched_param current;

      sched_getparam( 0, &current );
      current.sched_priority = DEFAULT_SCHED_PARAM;
      return sched_setscheduler( 0, in, &current );
#else
      return 0;
#endif
}

/****************************************************************************
 * Function: SetPriority
 *
 *  Description:
 *      Sets the priority of the currently running thread.
 *      Internal only.
 *  Parameters:
 *      ThreadPriority priority
 *  Returns:
 *      0 on success, nonzero on failure
 *      EINVAL invalid priority
 *      Returns result of GerLastError on failure.
 *
 *****************************************************************************/
static int SetPriority( ThreadPriority priority )
{
#if defined(_POSIX_PRIORITY_SCHEDULING) && _POSIX_PRIORITY_SCHEDULING > 0
      int currentPolicy;
      int minPriority = 0;
      int maxPriority = 0;
      int actPriority = 0;
      int midPriority = 0;
      struct sched_param newPriority;

      pthread_getschedparam( ithread_self(), &currentPolicy, &newPriority );
      minPriority = sched_get_priority_min( currentPolicy );
      maxPriority = sched_get_priority_max( currentPolicy );
      midPriority = ( maxPriority - minPriority ) / 2;
      switch ( priority ) {
      case LOW_PRIORITY:
            actPriority = minPriority;
            break;
      case MED_PRIORITY:
            actPriority = midPriority;
            break;
      case HIGH_PRIORITY:
            actPriority = maxPriority;
            break;
      default:
            return EINVAL;
      };

      newPriority.sched_priority = actPriority;

      return pthread_setschedparam(ithread_self(), currentPolicy, &newPriority );
#else
      return 0;
#endif
}

/****************************************************************************
 * Function: BumpPriority
 *
 *  Description:
 *      Determines whether any jobs
 *      need to be bumped to a higher priority Q and bumps them.
 *
 *      tp->mutex must be locked.
 *      Internal Only.
 *  Parameters:
 *      ThreadPool *tp
 *****************************************************************************/
static void BumpPriority( ThreadPool *tp )
{
    int done = 0;
    struct timeval now;
    unsigned long diffTime = 0;
    ThreadPoolJob *tempJob = NULL;

    assert( tp != NULL );

    gettimeofday(&now, NULL); 

    while( !done ) {
        if( tp->medJobQ.size ) {
            tempJob = ( ThreadPoolJob *) tp->medJobQ.head.next->item;
            diffTime = DiffMillis( &now, &tempJob->requestTime );
            if( diffTime >= ( tp->attr.starvationTime ) ) {
                // If job has waited longer than the starvation time
                // bump priority (add to higher priority Q)
                StatsAccountMQ( tp, diffTime );
                ListDelNode( &tp->medJobQ, tp->medJobQ.head.next, 0 );
                ListAddTail( &tp->highJobQ, tempJob );
                continue;
            }
        }
        if( tp->lowJobQ.size ) {
            tempJob = ( ThreadPoolJob *) tp->lowJobQ.head.next->item;
            diffTime = DiffMillis( &now, &tempJob->requestTime );
            if( diffTime >= ( tp->attr.maxIdleTime ) ) {
                // If job has waited longer than the starvation time
                // bump priority (add to higher priority Q)
                StatsAccountLQ( tp, diffTime );
                ListDelNode( &tp->lowJobQ, tp->lowJobQ.head.next, 0 );
                ListAddTail( &tp->medJobQ, tempJob );
                continue;
            }
        }
        done = 1;
    }
}

/****************************************************************************
 * Function: SetRelTimeout
 *
 *  Description:
 *      Sets the fields of the
 *      passed in timespec to be relMillis milliseconds in the future.
 *      Internal Only.
 *  Parameters:
 *      struct timespec *time
 *      int relMillis - milliseconds in the future
 *****************************************************************************/
static void SetRelTimeout( struct timespec *time, int relMillis )
{
      struct timeval now;
      int sec = relMillis / 1000;
      int milliSeconds = relMillis % 1000;

      assert( time != NULL );

      gettimeofday( &now, NULL );

      time->tv_sec = now.tv_sec + sec;
      time->tv_nsec = ( (now.tv_usec/1000) + milliSeconds ) * 1000000;
}

/****************************************************************************
 * Function: SetSeed
 *
 *  Description:
 *      Sets seed for random number generator.
 *      Each thread sets the seed random number generator.
 *      Internal Only.
 *  Parameters:
 *      
 *****************************************************************************/
static void SetSeed()
{
      struct timeval t;
  
      gettimeofday(&t, NULL);
#if defined(WIN32)
      srand( ( unsigned int )t.tv_usec + (unsigned int)ithread_get_current_thread_id().p );
#elif defined(__FreeBSD__) || defined(__OSX__) || defined(__APPLE__)
      srand( ( unsigned int )t.tv_usec + (unsigned int)ithread_get_current_thread_id() );
#elif defined(__linux__) || defined(__sun) || defined(__CYGWIN__)
      srand( ( unsigned int )t.tv_usec + ithread_get_current_thread_id() );
#else
      {
            volatile union { volatile pthread_t tid; volatile unsigned i; } idu;

            idu.tid = ithread_get_current_thread_id();
            srand( ( unsigned int )t.millitm + idu.i );
      }
#endif
}

/****************************************************************************
 * Function: WorkerThread
 *
 *  Description:
 *      Implements a thread pool worker.
 *      Worker waits for a job to become available.
 *      Worker picks up persistent jobs first, high priority, med priority,
 *             then low priority.
 *      If worker remains idle for more than specified max, the worker
 *      is released.
 *      Internal Only.
 *  Parameters:
 *      void * arg -> is cast to ThreadPool *
 *****************************************************************************/
static void *WorkerThread( void *arg )
{
      time_t start = 0;

      ThreadPoolJob *job = NULL;
      ListNode *head = NULL;

      struct timespec timeout;
      int retCode = 0;
      int persistent = -1;
      ThreadPool *tp = ( ThreadPool *) arg;
      // allow static linking
#ifdef WIN32
#ifdef PTW32_STATIC_LIB
      pthread_win32_thread_attach_np();
#endif
#endif
      assert( tp != NULL );

      // Increment total thread count
      ithread_mutex_lock( &tp->mutex );
      tp->totalThreads++;
      ithread_cond_broadcast( &tp->start_and_shutdown );
      ithread_mutex_unlock( &tp->mutex );

      SetSeed();
      StatsTime( &start );
      while( 1 ) {
            ithread_mutex_lock( &tp->mutex );
            if( job ) {
                  FreeThreadPoolJob( tp, job );
                  job = NULL;
            }
            retCode = 0;

            tp->stats.idleThreads++;
            tp->stats.totalWorkTime += ( StatsTime( NULL ) - start ); // work time
            StatsTime( &start ); // idle time

            if( persistent == 1 ) {
                  // Persistent thread
                  // becomes a regular thread
                  tp->persistentThreads--;
            }

            if( persistent == 0 ) {
                  tp->stats.workerThreads--;
            }

            // Check for a job or shutdown
            while( tp->lowJobQ.size  == 0 &&
                   tp->medJobQ.size  == 0 &&
                   tp->highJobQ.size == 0 &&
                   !tp->persistentJob     &&
                   !tp->shutdown ) {
                  // If wait timed out
                  // and we currently have more than the
                  // min threads, or if we have more than the max threads
                  // (only possible if the attributes have been reset)
                  // let this thread die.
                  if( ( retCode == ETIMEDOUT &&
                        tp->totalThreads > tp->attr.minThreads ) ||
                      ( tp->attr.maxThreads != -1 &&
                        tp->totalThreads > tp->attr.maxThreads ) ) {
                        tp->stats.idleThreads--;
                        tp->totalThreads--;
                        ithread_cond_broadcast( &tp->start_and_shutdown );
                        ithread_mutex_unlock( &tp->mutex );
#ifdef WIN32
#ifdef PTW32_STATIC_LIB
                        // allow static linking
                        pthread_win32_thread_detach_np ();
#endif
#endif
                        return NULL;
                  }
                  SetRelTimeout( &timeout, tp->attr.maxIdleTime );

                  // wait for a job up to the specified max time
                  retCode = ithread_cond_timedwait(
                        &tp->condition, &tp->mutex, &timeout );
            }

            tp->stats.idleThreads--;
            tp->stats.totalIdleTime += ( StatsTime( NULL ) - start ); // idle time
            StatsTime( &start ); // work time

            // bump priority of starved jobs
            BumpPriority( tp );

            // if shutdown then stop
            if( tp->shutdown ) {
                  tp->totalThreads--;
                  ithread_cond_broadcast( &tp->start_and_shutdown );
                  ithread_mutex_unlock( &tp->mutex );
#ifdef WIN32
#ifdef PTW32_STATIC_LIB
                  // allow static linking
                  pthread_win32_thread_detach_np ();
#endif
#endif
                  return NULL;
            } else {
                  // Pick up persistent job if available
                  if( tp->persistentJob ) {
                        job = tp->persistentJob;
                        tp->persistentJob = NULL;
                        tp->persistentThreads++;
                        persistent = 1;
                        ithread_cond_broadcast( &tp->start_and_shutdown );
                  } else {
                        tp->stats.workerThreads++;
                        persistent = 0;
                        // Pick the highest priority job
                        if( tp->highJobQ.size > 0 ) {
                              head = ListHead( &tp->highJobQ );
                              job = ( ThreadPoolJob *) head->item;
                              CalcWaitTime( tp, HIGH_PRIORITY, job );
                              ListDelNode( &tp->highJobQ, head, 0 );
                        } else if( tp->medJobQ.size > 0 ) {
                              head = ListHead( &tp->medJobQ );
                              job = ( ThreadPoolJob *) head->item;
                              CalcWaitTime( tp, MED_PRIORITY, job );
                              ListDelNode( &tp->medJobQ, head, 0 );
                        } else if( tp->lowJobQ.size > 0 ) {
                              head = ListHead( &tp->lowJobQ );
                              job = ( ThreadPoolJob *) head->item;
                              CalcWaitTime( tp, LOW_PRIORITY, job );
                              ListDelNode( &tp->lowJobQ, head, 0 );
                        } else {
                              // Should never get here
                              assert( 0 );
                              tp->stats.workerThreads--;
                              tp->totalThreads--;
                              ithread_cond_broadcast( &tp->start_and_shutdown );
                              ithread_mutex_unlock( &tp->mutex );

                              return NULL;
                        }
                  }
            }

            ithread_mutex_unlock( &tp->mutex );

            if( SetPriority( job->priority ) != 0 ) {
                  // In the future can log
                  // info
            } else {
                  // In the future can log
                  // info
            }

            // run the job
            job->func( job->arg );

            // return to Normal
            SetPriority( DEFAULT_PRIORITY );
      }
}

/****************************************************************************
 * Function: CreateThreadPoolJob
 *
 *  Description:
 *      Creates a Thread Pool Job. (Dynamically allocated)
 *      Internal to thread pool.
 *  Parameters:
 *      ThreadPoolJob *job - job is copied
 *      id - id of job
 *
 *  Returns:
 *      ThreadPoolJob *on success, NULL on failure.
 *****************************************************************************/
static ThreadPoolJob *CreateThreadPoolJob( ThreadPoolJob *job, int id, ThreadPool *tp )
{
      ThreadPoolJob *newJob = NULL;

      assert( job != NULL );
      assert( tp != NULL );

      newJob = (ThreadPoolJob *)FreeListAlloc( &tp->jobFreeList );
      if( newJob ) {
            *newJob = *job;
            newJob->jobId = id;
            gettimeofday( &newJob->requestTime, NULL );
      }

      return newJob;
}

/****************************************************************************
 * Function: CreateWorker
 *
 *  Description:
 *      Creates a worker thread, if the thread pool
 *      does not already have max threads.
 *      Internal to thread pool.
 *  Parameters:
 *      ThreadPool *tp
 *
 *  Returns:
 *      0 on success, <0 on failure
 *      EMAXTHREADS if already max threads reached
 *      EAGAIN if system can not create thread
 *
 *****************************************************************************/
static int CreateWorker( ThreadPool *tp )
{
      ithread_t temp;
      int rc = 0;
      int currentThreads = tp->totalThreads + 1;

      assert( tp != NULL );

      if ( tp->attr.maxThreads != INFINITE_THREADS &&
           currentThreads > tp->attr.maxThreads ) {
            return EMAXTHREADS;
      }

      rc = ithread_create( &temp, NULL, WorkerThread, tp );
      if( rc == 0 ) {
            rc = ithread_detach( temp );
            while( tp->totalThreads < currentThreads ) {
                  ithread_cond_wait( &tp->start_and_shutdown, &tp->mutex );
            }
      }

      if( tp->stats.maxThreads < tp->totalThreads ) {
            tp->stats.maxThreads = tp->totalThreads;
      }

      return rc;
}

/****************************************************************************
 * Function: AddWorker
 *
 *  Description:
 *      Determines whether or not a thread should be added
 *      based on the jobsPerThread ratio.
 *      Adds a thread if appropriate.
 *      Internal to Thread Pool.
 *  Parameters:
 *      ThreadPool* tp
 *
 *****************************************************************************/
static void AddWorker( ThreadPool *tp )
{
      int jobs = 0;
      int threads = 0;

      assert( tp != NULL );

      jobs = tp->highJobQ.size + tp->lowJobQ.size + tp->medJobQ.size;
      threads = tp->totalThreads - tp->persistentThreads;
      while( threads == 0 || (jobs / threads) >= tp->attr.jobsPerThread ) {
            if( CreateWorker( tp ) != 0 ) {
                  return;
            }
            threads++;
      }
}

/****************************************************************************
 * Function: ThreadPoolInit
 *
 *  Description:
 *      Initializes and starts ThreadPool. Must be called first.
 *      And only once for ThreadPool.
 *  Parameters:
 *      tp  - must be valid, non null, pointer to ThreadPool.
 *      minWorkerThreads - minimum number of worker threads
 *                         thread pool will never have less than this
 *                         number of threads.
 *      maxWorkerThreads - maximum number of worker threads
 *                         thread pool will never have more than this
 *                         number of threads.
 *      maxIdleTime      - maximum time that a worker thread will spend
 *                         idle. If a worker is idle longer than this
 *                         time and there are more than the min
 *                         number of workers running, than the
 *                         worker thread exits.
 *      jobsPerThread    - ratio of jobs to thread to try and maintain
 *                         if a job is scheduled and the number of jobs per
 *                         thread is greater than this number,and
 *                         if less than the maximum number of
 *                         workers are running then a new thread is
 *                         started to help out with efficiency.
 *      schedPolicy      - scheduling policy to try and set (OS dependent)
 *  Returns:
 *      0 on success, nonzero on failure.
 *      EAGAIN if not enough system resources to create minimum threads.
 *      INVALID_POLICY if schedPolicy can't be set
 *      EMAXTHREADS if minimum threads is greater than maximum threads
 *****************************************************************************/
int ThreadPoolInit( ThreadPool *tp, ThreadPoolAttr *attr )
{
      int retCode = 0;
      int i = 0;

      assert( tp != NULL );
      if( tp == NULL ) {
            return EINVAL;
      }
#ifdef WIN32
#ifdef PTW32_STATIC_LIB
      pthread_win32_process_attach_np();
#endif
#endif

      retCode += ithread_mutex_init( &tp->mutex, NULL );
      assert( retCode == 0 );

      retCode += ithread_mutex_lock( &tp->mutex );
      assert( retCode == 0 );

      retCode += ithread_cond_init( &tp->condition, NULL );
      assert( retCode == 0 );

      retCode += ithread_cond_init( &tp->start_and_shutdown, NULL );
      assert( retCode == 0 );

      if( retCode != 0 ) {
            return EAGAIN;
      }

      if( attr ) {
            tp->attr = ( *attr );
      } else {
            TPAttrInit( &tp->attr );
      }

      if( SetPolicyType( tp->attr.schedPolicy ) != 0 ) {
            ithread_mutex_unlock( &tp->mutex );
            ithread_mutex_destroy( &tp->mutex );
            ithread_cond_destroy( &tp->condition );
            ithread_cond_destroy( &tp->start_and_shutdown );
            return INVALID_POLICY;
      }

      retCode += FreeListInit(
            &tp->jobFreeList, sizeof( ThreadPoolJob ), JOBFREELISTSIZE );
      assert( retCode == 0 );

      StatsInit( &tp->stats );

      retCode += ListInit( &tp->highJobQ, CmpThreadPoolJob, NULL );
      assert( retCode == 0 );

      retCode += ListInit( &tp->medJobQ, CmpThreadPoolJob, NULL );
      assert( retCode == 0 );

      retCode += ListInit( &tp->lowJobQ, CmpThreadPoolJob, NULL );
      assert( retCode == 0 );

      if( retCode != 0 ) {
            retCode = EAGAIN;
      } else {
            tp->persistentJob = NULL;
            tp->lastJobId = 0;
            tp->shutdown = 0;
            tp->totalThreads = 0;
            tp->persistentThreads = 0;
            for( i = 0; i < tp->attr.minThreads; ++i ) {
                  if( ( retCode = CreateWorker( tp ) ) != 0 ) {
                        break;
                  }
            }
      }

      ithread_mutex_unlock( &tp->mutex );

      if( retCode != 0 ) {
            // clean up if the min threads could not be created
            ThreadPoolShutdown( tp );
      }

      return retCode;
}

/****************************************************************************
 * Function: ThreadPoolAddPersistent
 *
 *  Description:
 *      Adds a long term job to the thread pool.
 *      Job will be run as soon as possible.
 *      Call will block until job is scheduled.
 *  Parameters:
 *      tp - valid thread pool pointer
 *      job-> valid ThreadPoolJob pointer with following fields
 *          func - ThreadFunction to run
 *          arg - argument to function.
 *          priority - priority of job.
 *          free_function - function to use when freeing argument
 *  Returns:
 *      0 on success, nonzero on failure
 *      EOUTOFMEM not enough memory to add job.
 *      EMAXTHREADS not enough threads to add persistent job.
 *****************************************************************************/
int ThreadPoolAddPersistent( ThreadPool *tp, ThreadPoolJob *job, int *jobId )
{
      int tempId = -1;
      ThreadPoolJob *temp = NULL;

      assert( tp != NULL );
      assert( job != NULL );
      if( ( tp == NULL ) || ( job == NULL ) ) {
            return EINVAL;
      }

      if( jobId == NULL ) {
            jobId = &tempId;
      }

      *jobId = INVALID_JOB_ID;

      ithread_mutex_lock( &tp->mutex );

      assert( job->priority == LOW_PRIORITY ||
              job->priority == MED_PRIORITY ||
              job->priority == HIGH_PRIORITY );

      // Create A worker if less than max threads running
      if( tp->totalThreads < tp->attr.maxThreads ) {
            CreateWorker( tp );
      } else {
            // if there is more than one worker thread
            // available then schedule job, otherwise fail
            if( tp->totalThreads - tp->persistentThreads - 1 == 0 ) {
                  ithread_mutex_unlock( &tp->mutex );
                  return EMAXTHREADS;
            }
      }

      temp = CreateThreadPoolJob( job, tp->lastJobId, tp );
      if( temp == NULL ) {
            ithread_mutex_unlock( &tp->mutex );
            return EOUTOFMEM;
      }

      tp->persistentJob = temp;

      // Notify a waiting thread
      ithread_cond_signal( &tp->condition );

      // wait until long job has been picked up
      while( tp->persistentJob != NULL ) {
            ithread_cond_wait( &tp->start_and_shutdown, &tp->mutex );
      }

      *jobId = tp->lastJobId++;
      ithread_mutex_unlock( &tp->mutex );

      return 0;
}

/****************************************************************************
 * Function: ThreadPoolAdd
 *
 *  Description:
 *      Adds a job to the thread pool.
 *      Job will be run as soon as possible.
 *  Parameters:
 *      tp - valid thread pool pointer
 *      func - ThreadFunction to run
 *      arg - argument to function.
 *      priority - priority of job.
 *      jobId - id of job
 *      duration - whether or not this is a persistent thread
 *      free_function - function to use when freeing argument
 *  Returns:
 *      0 on success, nonzero on failure
 *      EOUTOFMEM if not enough memory to add job.
 *****************************************************************************/
int ThreadPoolAdd( ThreadPool *tp, ThreadPoolJob *job, int *jobId )
{
      int rc = EOUTOFMEM;

      int tempId = -1;
      int totalJobs;

      ThreadPoolJob *temp = NULL;

      assert( tp != NULL );
      assert( job != NULL );
      if( ( tp == NULL ) || ( job == NULL ) ) {
            return EINVAL;
      }

      ithread_mutex_lock( &tp->mutex );

      assert( job->priority == LOW_PRIORITY ||
      job->priority == MED_PRIORITY ||
      job->priority == HIGH_PRIORITY );

      totalJobs = tp->highJobQ.size + tp->lowJobQ.size + tp->medJobQ.size;
      if (totalJobs >= tp->attr.maxJobsTotal) {
            fprintf(stderr, "total jobs = %d, too many jobs", totalJobs);
            ithread_mutex_unlock( &tp->mutex );
            return rc;
      }

      if( jobId == NULL ) {
            jobId = &tempId;
      }
      *jobId = INVALID_JOB_ID;

      temp = CreateThreadPoolJob( job, tp->lastJobId, tp );
      if( temp == NULL ) {
            ithread_mutex_unlock( &tp->mutex );
            return rc;
      }

      if( job->priority == HIGH_PRIORITY ) {
            if( ListAddTail( &tp->highJobQ, temp ) ) {
                  rc = 0;
            }
      } else if( job->priority == MED_PRIORITY ) {
            if( ListAddTail( &tp->medJobQ, temp ) ) {
                  rc = 0;
            }
      } else {
            if( ListAddTail( &tp->lowJobQ, temp ) ) {
                  rc = 0;
            }
      }

      // AddWorker if appropriate
      AddWorker( tp );

      // Notify a waiting thread
      if( rc == 0 ) {
            ithread_cond_signal( &tp->condition );
      } else {
            FreeThreadPoolJob( tp, temp );
      }

      *jobId = tp->lastJobId++;

      ithread_mutex_unlock( &tp->mutex );

      return rc;
}

/****************************************************************************
 * Function: ThreadPoolRemove
 *
 *  Description:
 *      Removes a job from the thread pool.
 *      Can only remove jobs which are not
 *      currently running.
 *  Parameters:
 *      tp - valid thread pool pointer
 *      jobId - id of job
 *      ThreadPoolJob *out - space for removed job.
 *                           Can be null if not needed.
 *
 *  Returns:
 *      0 on success. INVALID_JOB_ID on failure.
 *****************************************************************************/
int ThreadPoolRemove( ThreadPool *tp, int jobId, ThreadPoolJob *out )
{
      ThreadPoolJob *temp = NULL;
      int ret = INVALID_JOB_ID;
      ListNode *tempNode = NULL;
      ThreadPoolJob dummy;

      assert( tp != NULL );
      if( tp == NULL ) {
            return EINVAL;
      }

      if( out == NULL ) {
            out = &dummy;
      }

      dummy.jobId = jobId;

      ithread_mutex_lock( &tp->mutex );

      tempNode = ListFind( &tp->highJobQ, NULL, &dummy );
      if( tempNode ) {
            temp = (ThreadPoolJob *)tempNode->item;
            *out = *temp;
            ListDelNode( &tp->highJobQ, tempNode, 0 );
            FreeThreadPoolJob( tp, temp );
            ithread_mutex_unlock( &tp->mutex );

            return 0;
      }

      tempNode = ListFind( &tp->medJobQ, NULL, &dummy );
      if( tempNode ) {
            temp = (ThreadPoolJob *)tempNode->item;
            *out = *temp;
            ListDelNode( &tp->medJobQ, tempNode, 0 );
            FreeThreadPoolJob( tp, temp );
            ithread_mutex_unlock( &tp->mutex );

            return 0;
      }

      tempNode = ListFind( &tp->lowJobQ, NULL, &dummy );
      if( tempNode ) {
            temp = (ThreadPoolJob *)tempNode->item;
            *out = *temp;
            ListDelNode( &tp->lowJobQ, tempNode, 0 );
            FreeThreadPoolJob( tp, temp );
            ithread_mutex_unlock( &tp->mutex );

            return 0;
      }

      if( tp->persistentJob && tp->persistentJob->jobId == jobId ) {
            *out = *tp->persistentJob;
            FreeThreadPoolJob( tp, tp->persistentJob );
            tp->persistentJob = NULL;
            ithread_mutex_unlock( &tp->mutex );

            return 0;
      }

      ithread_mutex_unlock( &tp->mutex );

      return ret;
}

/****************************************************************************
 * Function: ThreadPoolGetAttr
 *
 *  Description:
 *      Gets the current set of attributes
 *      associated with the thread pool.
 *  Parameters:
 *      tp - valid thread pool pointer
 *      out - non null pointer to store attributes
 *  Returns:
 *      0 on success, nonzero on failure
 *      Always returns 0.
 *****************************************************************************/
int ThreadPoolGetAttr( ThreadPool *tp, ThreadPoolAttr *out )
{
      assert( tp != NULL );
      assert( out != NULL );
      if( tp == NULL || out == NULL ) {
            return EINVAL;
      }

      if( !tp->shutdown ) {
            ithread_mutex_lock( &tp->mutex );
      }

      *out = tp->attr;

      if( !tp->shutdown ) {
            ithread_mutex_unlock( &tp->mutex );
      }

      return 0;
}

/****************************************************************************
 * Function: ThreadPoolSetAttr
 *
 *  Description:
 *      Sets the attributes for the thread pool.
 *      Only affects future calculations.
 *  Parameters:
 *      tp - valid thread pool pointer
 *      attr - pointer to attributes, null sets attributes to default.
 *  Returns:
 *      0 on success, nonzero on failure
 *      Returns INVALID_POLICY if policy can not be set.
 *****************************************************************************/
int ThreadPoolSetAttr( ThreadPool *tp, ThreadPoolAttr *attr )
{
      int retCode = 0;
      ThreadPoolAttr temp;
      int i = 0;

      assert( tp != NULL );
      if( tp == NULL ) {
            return EINVAL;
      }
      ithread_mutex_lock( &tp->mutex );

      if( attr != NULL ) {
            temp = ( *attr );
      } else {
            TPAttrInit( &temp );
      }

      if( SetPolicyType( temp.schedPolicy ) != 0 ) {
            ithread_mutex_unlock( &tp->mutex );

            return INVALID_POLICY;
      }

      tp->attr = ( temp );

      // add threads
      if( tp->totalThreads < tp->attr.minThreads )
      {
            for( i = tp->totalThreads; i < tp->attr.minThreads; i++ ) {
                  if( ( retCode = CreateWorker( tp ) ) != 0 ) {
                        break;
                  }
            }
      }

      // signal changes 
      ithread_cond_signal( &tp->condition ); 
      ithread_mutex_unlock( &tp->mutex );

      if( retCode != 0 ) {
            // clean up if the min threads could not be created
            ThreadPoolShutdown( tp );
      }

      return retCode;
}

/****************************************************************************
 * Function: ThreadPoolShutdown
 *
 *  Description:
 *      Shuts the thread pool down.
 *      Waits for all threads to finish.
 *      May block indefinitely if jobs do not
 *      exit.
 *  Parameters:
 *      tp - must be valid tp
 *  Returns:
 *      0 on success, nonzero on failure
 *      Always returns 0.
 *****************************************************************************/
int ThreadPoolShutdown( ThreadPool *tp )
{
      ListNode *head = NULL;
      ThreadPoolJob *temp = NULL;

      assert( tp != NULL );
      if( tp == NULL ) {
            return EINVAL;
      }

      ithread_mutex_lock( &tp->mutex );

      // clean up high priority jobs
      while( tp->highJobQ.size ) {
            head = ListHead( &tp->highJobQ );
            temp = ( ThreadPoolJob *) head->item;
            if( temp->free_func ) {
                  temp->free_func( temp->arg );
            }
            FreeThreadPoolJob( tp, temp );
            ListDelNode( &tp->highJobQ, head, 0 );
      }
      ListDestroy( &tp->highJobQ, 0 );

      // clean up med priority jobs
      while( tp->medJobQ.size ) {
            head = ListHead( &tp->medJobQ );
            temp = ( ThreadPoolJob *) head->item;
            if( temp->free_func ) {
                  temp->free_func( temp->arg );
            }
            FreeThreadPoolJob( tp, temp );
            ListDelNode( &tp->medJobQ, head, 0 );
      }
      ListDestroy( &tp->medJobQ, 0 );

      // clean up low priority jobs
            while( tp->lowJobQ.size ) {
            head = ListHead( &tp->lowJobQ );
            temp = ( ThreadPoolJob *) head->item;
            if( temp->free_func ) {
                  temp->free_func( temp->arg );
            }
            FreeThreadPoolJob( tp, temp );
            ListDelNode( &tp->lowJobQ, head, 0 );
      }
      ListDestroy( &tp->lowJobQ, 0 );

      // clean up long term job
      if( tp->persistentJob ) {
            temp = tp->persistentJob;
            if( temp->free_func ) {
                  temp->free_func( temp->arg );
            }
            FreeThreadPoolJob( tp, temp );
            tp->persistentJob = NULL;
      }

      // signal shutdown
      tp->shutdown = 1;
      ithread_cond_broadcast( &tp->condition );

      // wait for all threads to finish
      while( tp->totalThreads > 0 ) {
            ithread_cond_wait( &tp->start_and_shutdown, &tp->mutex );
      }

      // destroy condition
      while( ithread_cond_destroy( &tp->condition ) != 0 ) {
      }
      while( ithread_cond_destroy( &tp->start_and_shutdown ) != 0 ) {
      }

      FreeListDestroy( &tp->jobFreeList );

      ithread_mutex_unlock( &tp->mutex );

      // destroy mutex
      while( ithread_mutex_destroy( &tp->mutex ) != 0 ) {
      }

      return 0;
}

/****************************************************************************
 * Function: TPAttrInit
 *
 *  Description:
 *      Initializes thread pool attributes.
 *      Sets values to defaults defined in ThreadPool.h.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrInit( ThreadPoolAttr *attr )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->jobsPerThread  = DEFAULT_JOBS_PER_THREAD;
      attr->maxIdleTime    = DEFAULT_IDLE_TIME;
      attr->maxThreads     = DEFAULT_MAX_THREADS;
      attr->minThreads     = DEFAULT_MIN_THREADS;
      attr->schedPolicy    = DEFAULT_POLICY;
      attr->starvationTime = DEFAULT_STARVATION_TIME;
      attr->maxJobsTotal   = DEFAULT_MAX_JOBS_TOTAL;

      return 0;
}

/****************************************************************************
 * Function: TPJobInit
 *
 *  Description:
 *      Initializes thread pool job.
 *      Sets the priority to default defined in ThreadPool.h.
 *      Sets the free_routine to default defined in ThreadPool.h
 *  Parameters:
 *      ThreadPoolJob *job - must be valid thread pool attributes.
 *      start_routine func - function to run, must be valid
 *      void * arg - argument to pass to function.
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPJobInit( ThreadPoolJob *job, start_routine func, void *arg )
{
      assert( job != NULL );
      assert( func != NULL );
      if( job == NULL || func == NULL ) {
            return EINVAL;
      }

      job->func = func;
      job->arg = arg;
      job->priority = DEFAULT_PRIORITY;
      job->free_func = DEFAULT_FREE_ROUTINE;

      return 0;
}

/****************************************************************************
 * Function: TPJobSetPriority
 *
 *  Description:
 *      Sets the max threads for the thread pool attributes.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *      maxThreads - value to set
 *  Returns:
 *      Returns 0 on success nonzero on failure.
 *      Returns EINVAL if invalid priority.
 *****************************************************************************/
int TPJobSetPriority(ThreadPoolJob *job, ThreadPriority priority )
{
      assert( job != NULL );
      if( job == NULL ) {
            return EINVAL;
      }

      if( priority == LOW_PRIORITY ||
          priority == MED_PRIORITY ||
          priority == HIGH_PRIORITY ) {
            job->priority = priority;
            return 0;
      } else {
            return EINVAL;
      }
}

/****************************************************************************
 * Function: TPJobSetFreeFunction
 *
 *  Description:
 *      Sets the max threads for the thread pool attributes.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *      maxThreads - value to set
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPJobSetFreeFunction( ThreadPoolJob *job, free_routine func )
{
      assert( job != NULL );
      if( job == NULL ) {
            return EINVAL;
      }

      job->free_func = func;

      return 0;
}

/****************************************************************************
 * Function: TPAttrSetMaxThreads
 *
 *  Description:
 *      Sets the max threads for the thread pool attributes.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *      maxThreads - value to set
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrSetMaxThreads( ThreadPoolAttr *attr, int maxThreads )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->maxThreads = maxThreads;

      return 0;
}

/****************************************************************************
 * Function: TPAttrSetMinThreads
 *
 *  Description:
 *      Sets the min threads for the thread pool attributes.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *      minThreads - value to set
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrSetMinThreads( ThreadPoolAttr *attr, int minThreads )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->minThreads = minThreads;

      return 0;
}

/****************************************************************************
 * Function: TPAttrSetIdleTime
 *
 *  Description:
 *      Sets the idle time for the thread pool attributes.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrSetIdleTime( ThreadPoolAttr *attr, int idleTime )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->maxIdleTime = idleTime;

      return 0;
}

/****************************************************************************
 * Function: TPAttrSetJobsPerThread
 *
 *  Description:
 *      Sets the max thre
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrSetJobsPerThread( ThreadPoolAttr *attr, int jobsPerThread )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->jobsPerThread = jobsPerThread;

      return 0;
}

/****************************************************************************
 * Function: TPAttrSetStarvationTime
 *
 *  Description:
 *      Sets the starvation time for the thread pool attributes.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrSetStarvationTime( ThreadPoolAttr *attr, int starvationTime )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->starvationTime = starvationTime;

      return 0;
}

/****************************************************************************
 * Function: TPAttrSetSchedPolicy
 *
 *  Description:
 *      Sets the scheduling policy for the thread pool attributes.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *      PolicyType schedPolicy - must be a valid policy type.
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrSetSchedPolicy( ThreadPoolAttr *attr, PolicyType schedPolicy )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->schedPolicy = schedPolicy;

      return 0;
}

/****************************************************************************
 * Function: TPAttrSetMaxJobsTotal
 *
 *  Description:
 *      Sets the maximum number jobs that can be qeued totally.
 *  Parameters:
 *      attr - must be valid thread pool attributes.
 *      maxJobsTotal - maximum number of jobs
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
int TPAttrSetMaxJobsTotal( ThreadPoolAttr *attr, int  maxJobsTotal )
{
      assert( attr != NULL );
      if( attr == NULL ) {
            return EINVAL;
      }

      attr->maxJobsTotal = maxJobsTotal;

      return 0;
}


#ifdef STATS
void ThreadPoolPrintStats(ThreadPoolStats *stats)
{
      assert( stats != NULL );
      if (stats == NULL) {
            return;
      }

#ifdef __FreeBSD__
      printf("ThreadPoolStats at Time: %d\n", StatsTime(NULL));
#else /* __FreeBSD__ */
      printf("ThreadPoolStats at Time: %ld\n", StatsTime(NULL));
#endif /* __FreeBSD__ */
      printf("High Jobs pending: %d\n", stats->currentJobsHQ);
      printf("Med Jobs Pending: %d\n", stats->currentJobsMQ);
      printf("Low Jobs Pending: %d\n", stats->currentJobsLQ);
      printf("Average Wait in High Priority Q in milliseconds: %f\n", stats->avgWaitHQ);
      printf("Average Wait in Med Priority Q in milliseconds: %f\n", stats->avgWaitMQ);
      printf("Averate Wait in Low Priority Q in milliseconds: %f\n", stats->avgWaitLQ);
      printf("Max Threads Active: %d\n", stats->maxThreads);
      printf("Current Worker Threads: %d\n", stats->workerThreads);
      printf("Current Persistent Threads: %d\n", stats->persistentThreads);
      printf("Current Idle Threads: %d\n", stats->idleThreads);
      printf("Total Threads : %d\n", stats->totalThreads);
      printf("Total Time spent Working in seconds: %f\n", stats->totalWorkTime);
      printf("Total Time spent Idle in seconds : %f\n", stats->totalIdleTime);
}
#endif /* STATS */


/****************************************************************************
 * Function: ThreadPoolGetStats
 *
 *  Description:
 *      Returns various statistics about the
 *      thread pool.
 *      Only valid if STATS has been defined.
 *  Parameters:
 *      ThreadPool *tp - valid initialized threadpool
 *      ThreadPoolStats *stats - valid stats, out parameter
 *  Returns:
 *      Always returns 0.
 *****************************************************************************/
#ifdef STATS
int ThreadPoolGetStats( ThreadPool *tp, ThreadPoolStats *stats )
{
      assert(tp != NULL);
      assert(stats != NULL);
      if (tp == NULL || stats == NULL) {
            return EINVAL;
      }

      //if not shutdown then acquire mutex
      if (!tp->shutdown) {
            ithread_mutex_lock(&tp->mutex);
      }

      *stats = tp->stats;
      if (stats->totalJobsHQ > 0) {
            stats->avgWaitHQ = stats->totalTimeHQ / stats->totalJobsHQ;
      } else {
            stats->avgWaitHQ = 0;
      }
      
      if( stats->totalJobsMQ > 0 ) {
            stats->avgWaitMQ = stats->totalTimeMQ / stats->totalJobsMQ;
      } else {
            stats->avgWaitMQ = 0;
      }
      
      if( stats->totalJobsLQ > 0 ) {
            stats->avgWaitLQ = stats->totalTimeLQ / stats->totalJobsLQ;
      } else {
            stats->avgWaitLQ = 0;
      }

      stats->totalThreads = tp->totalThreads;
      stats->persistentThreads = tp->persistentThreads;
      stats->currentJobsHQ = ListSize( &tp->highJobQ );
      stats->currentJobsLQ = ListSize( &tp->lowJobQ );
      stats->currentJobsMQ = ListSize( &tp->medJobQ );

      //if not shutdown then release mutex
      if( !tp->shutdown ) {
            ithread_mutex_unlock( &tp->mutex );
      }

      return 0;
}

#endif /* STATS */


#ifdef WIN32
#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
      #define DELTA_EPOCH_IN_MICROSECS  11644473600000000Ui64
#else
      #define DELTA_EPOCH_IN_MICROSECS  11644473600000000ULL
#endif
 
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
    FILETIME ft;
    unsigned __int64 tmpres = 0;
    static int tzflag;

    if (NULL != tv)
    {
        GetSystemTimeAsFileTime(&ft);

        tmpres |= ft.dwHighDateTime;
        tmpres <<= 32;
        tmpres |= ft.dwLowDateTime;

        /*converting file time to unix epoch*/
        tmpres /= 10;  /*convert into microseconds*/
        tmpres -= DELTA_EPOCH_IN_MICROSECS; 
        tv->tv_sec = (long)(tmpres / 1000000UL);
        tv->tv_usec = (long)(tmpres % 1000000UL);
    }

    if (NULL != tz)
    {
        if (!tzflag)
        {
            _tzset();
            tzflag++;
        }
        tz->tz_minuteswest = _timezone / 60;
        tz->tz_dsttime = _daylight;
    }

    return 0;
}
#endif /* WIN32 */


Generated by  Doxygen 1.6.0   Back to index