/* timers.c - simple timer routines ** ** Copyright © 1995,1998,2000,2014 by Jef Poskanzer . ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. 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. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR 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. */ #include #include #include #include #include "timers.h" #define HASH_SIZE 67 static Timer* timers[HASH_SIZE]; static Timer* free_timers; static int alloc_count, active_count, free_count; ClientData JunkClientData; static unsigned int hash( Timer* t ) { /* We can hash on the trigger time, even though it can change over ** the life of a timer via either the periodic bit or the tmr_reset() ** call. This is because both of those guys call l_resort(), which ** recomputes the hash and moves the timer to the appropriate list. */ return ( (unsigned int) t->time.tv_sec ^ (unsigned int) t->time.tv_usec ) % HASH_SIZE; } static void l_add( Timer* t ) { int h = t->hash; Timer* t2; Timer* t2prev; t2 = timers[h]; if ( t2 == (Timer*) 0 ) { /* The list is empty. */ timers[h] = t; t->prev = t->next = (Timer*) 0; } else { if ( t->time.tv_sec < t2->time.tv_sec || ( t->time.tv_sec == t2->time.tv_sec && t->time.tv_usec <= t2->time.tv_usec ) ) { /* The new timer goes at the head of the list. */ timers[h] = t; t->prev = (Timer*) 0; t->next = t2; t2->prev = t; } else { /* Walk the list to find the insertion point. */ for ( t2prev = t2, t2 = t2->next; t2 != (Timer*) 0; t2prev = t2, t2 = t2->next ) { if ( t->time.tv_sec < t2->time.tv_sec || ( t->time.tv_sec == t2->time.tv_sec && t->time.tv_usec <= t2->time.tv_usec ) ) { /* Found it. */ t2prev->next = t; t->prev = t2prev; t->next = t2; t2->prev = t; return; } } /* Oops, got to the end of the list. Add to tail. */ t2prev->next = t; t->prev = t2prev; t->next = (Timer*) 0; } } } static void l_remove( Timer* t ) { int h = t->hash; if ( t->prev == (Timer*) 0 ) timers[h] = t->next; else t->prev->next = t->next; if ( t->next != (Timer*) 0 ) t->next->prev = t->prev; } static void l_resort( Timer* t ) { /* Remove the timer from its old list. */ l_remove( t ); /* Recompute the hash. */ t->hash = hash( t ); /* And add it back in to its new list, sorted correctly. */ l_add( t ); } void tmr_init( void ) { int h; for ( h = 0; h < HASH_SIZE; ++h ) timers[h] = (Timer*) 0; free_timers = (Timer*) 0; alloc_count = active_count = free_count = 0; } Timer* tmr_create( struct timeval* nowP, TimerProc* timer_proc, ClientData client_data, long msecs, int periodic ) { Timer* t; if ( free_timers != (Timer*) 0 ) { t = free_timers; free_timers = t->next; --free_count; } else { t = (Timer*) malloc( sizeof(Timer) ); if ( t == (Timer*) 0 ) return (Timer*) 0; ++alloc_count; } t->timer_proc = timer_proc; t->client_data = client_data; t->msecs = msecs; t->periodic = periodic; if ( nowP != (struct timeval*) 0 ) t->time = *nowP; else (void) gettimeofday( &t->time, (struct timezone*) 0 ); t->time.tv_sec += msecs / 1000L; t->time.tv_usec += ( msecs % 1000L ) * 1000L; if ( t->time.tv_usec >= 1000000L ) { t->time.tv_sec += t->time.tv_usec / 1000000L; t->time.tv_usec %= 1000000L; } t->hash = hash( t ); /* Add the new timer to the proper active list. */ l_add( t ); ++active_count; return t; } struct timeval* tmr_timeout( struct timeval* nowP ) { long msecs; static struct timeval timeout; msecs = tmr_mstimeout( nowP ); if ( msecs == INFTIM ) return (struct timeval*) 0; timeout.tv_sec = msecs / 1000L; timeout.tv_usec = ( msecs % 1000L ) * 1000L; return &timeout; } long tmr_mstimeout( struct timeval* nowP ) { int h; int gotone; long msecs, m; Timer* t; gotone = 0; msecs = 0; /* make lint happy */ /* Since the lists are sorted, we only need to look at the ** first timer on each one. */ for ( h = 0; h < HASH_SIZE; ++h ) { t = timers[h]; if ( t != (Timer*) 0 ) { m = ( t->time.tv_sec - nowP->tv_sec ) * 1000L + ( t->time.tv_usec - nowP->tv_usec ) / 1000L; if ( ! gotone ) { msecs = m; gotone = 1; } else if ( m < msecs ) msecs = m; } } if ( ! gotone ) return INFTIM; if ( msecs <= 0 ) msecs = 0; return msecs; } void tmr_run( struct timeval* nowP ) { int h; Timer* t; Timer* next; for ( h = 0; h < HASH_SIZE; ++h ) for ( t = timers[h]; t != (Timer*) 0; t = next ) { next = t->next; /* Since the lists are sorted, as soon as we find a timer ** that isn't ready yet, we can go on to the next list. */ if ( t->time.tv_sec > nowP->tv_sec || ( t->time.tv_sec == nowP->tv_sec && t->time.tv_usec > nowP->tv_usec ) ) break; (t->timer_proc)( t->client_data, nowP ); if ( t->periodic ) { /* Reschedule. */ t->time.tv_sec += t->msecs / 1000L; t->time.tv_usec += ( t->msecs % 1000L ) * 1000L; if ( t->time.tv_usec >= 1000000L ) { t->time.tv_sec += t->time.tv_usec / 1000000L; t->time.tv_usec %= 1000000L; } l_resort( t ); } else tmr_cancel( t ); } } void tmr_reset( struct timeval* nowP, Timer* t ) { t->time = *nowP; t->time.tv_sec += t->msecs / 1000L; t->time.tv_usec += ( t->msecs % 1000L ) * 1000L; if ( t->time.tv_usec >= 1000000L ) { t->time.tv_sec += t->time.tv_usec / 1000000L; t->time.tv_usec %= 1000000L; } l_resort( t ); } void tmr_cancel( Timer* t ) { /* Remove it from its active list. */ l_remove( t ); --active_count; /* And put it on the free list. */ t->next = free_timers; free_timers = t; ++free_count; t->prev = (Timer*) 0; } void tmr_cleanup( void ) { Timer* t; while ( free_timers != (Timer*) 0 ) { t = free_timers; free_timers = t->next; --free_count; free( (void*) t ); --alloc_count; } } void tmr_term( void ) { int h; for ( h = 0; h < HASH_SIZE; ++h ) while ( timers[h] != (Timer*) 0 ) tmr_cancel( timers[h] ); tmr_cleanup(); } /* Generate debugging statistics syslog message. */ void tmr_logstats( long secs ) { syslog( LOG_NOTICE, " timers - %d allocated, %d active, %d free", alloc_count, active_count, free_count ); if ( active_count + free_count != alloc_count ) syslog( LOG_ERR, "timer counts don't add up!" ); }