trema/trema-edge

View on GitHub
src/lib/timer.c

Summary

Maintainability
Test Coverage
/*
 * Author: Yasuhito Takamiya <yasuhito@gmail.com>
 *
 * Copyright (C) 2008-2013 NEC Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <assert.h>
#include <errno.h>
#include <limits.h>
#include "doubly_linked_list.h"
#include "log.h"
#include "timer.h"
#include "wrapper.h"


#ifdef UNIT_TESTING

#ifdef clock_gettime
#undef clock_gettime
#endif
#define clock_gettime mock_clock_gettime
extern int mock_clock_gettime( clockid_t clk_id, struct timespec *tp );

#ifdef error
#undef error
#endif
#define error mock_error
void mock_error( const char *format, ... );

#ifdef debug
#undef debug
#endif
#define debug mock_debug
void mock_debug( const char *format, ... );

#define static

#endif // UNIT_TESTING


typedef struct timer_callback_info {
  timer_callback function;
  struct timespec expires_at;
  struct timespec interval;
  void *user_data;
} timer_callback_info;


static dlist_element *timer_callbacks = NULL;


bool
_init_timer() {
  if ( timer_callbacks != NULL ) {
    error( "Called init_timer twice." );
    return false;
  }

  timer_callbacks = create_dlist();

  debug( "Initializing timer callbacks ( timer_callbacks = %p ).", timer_callbacks );
  return true;
}
bool ( *init_timer )( void ) = _init_timer;


bool
_finalize_timer() {
  dlist_element *e;

  debug( "Deleting timer callbacks ( timer_callbacks = %p ).", timer_callbacks );

  if ( timer_callbacks != NULL ) {
    for ( e = timer_callbacks->next; e; e = e->next ) {
      xfree( e->data );
    }
    delete_dlist( timer_callbacks );
    timer_callbacks = NULL;
  }
  else {
    error( "All timer callbacks are already deleted or not created yet." );
  }
  return true;
}
bool ( *finalize_timer )( void ) = _finalize_timer;


#define VALID_TIMESPEC( _a )                                  \
  ( ( ( _a )->tv_sec > 0 || ( _a )->tv_nsec > 0 ) ? 1 : 0 )

#define ADD_TIMESPEC( _a, _b, _return )                       \
  do {                                                        \
    ( _return )->tv_sec = ( _a )->tv_sec + ( _b )->tv_sec;    \
    ( _return )->tv_nsec = ( _a )->tv_nsec + ( _b )->tv_nsec; \
    if ( ( _return )->tv_nsec >= 1000000000 ) {               \
      ( _return )->tv_sec++;                                  \
      ( _return )->tv_nsec -= 1000000000;                     \
    }                                                         \
  }                                                           \
  while ( 0 )

#define SUB_TIMESPEC( _a, _b, _return )                       \
  do {                                                        \
    ( _return )->tv_sec = ( _a )->tv_sec - ( _b )->tv_sec;    \
    ( _return )->tv_nsec = ( _a )->tv_nsec - ( _b )->tv_nsec; \
    if ( ( _return )->tv_nsec < 0 ) {                         \
      ( _return )->tv_sec--;                                  \
      ( _return )->tv_nsec += 1000000000;                     \
    }                                                         \
  }                                                           \
  while ( 0 )

#define TIMESPEC_LESS_THEN( _a, _b )                          \
  ( ( ( _a )->tv_sec == ( _b )->tv_sec ) ?                    \
    ( ( _a )->tv_nsec < ( _b )->tv_nsec ) :                   \
    ( ( _a )->tv_sec < ( _b )->tv_sec ) )

#define TIMESPEC_TO_MICROSECONDS( _a, _b )                    \
  do {                                                        \
    struct timespec round = { 0, 999 };                       \
    ADD_TIMESPEC( _a, &round, &round );                       \
    ( *( _b ) = ( int ) ( round.tv_sec * 1000000 + round.tv_nsec / 1000 ) ); \
  }                                                           \
  while ( 0 )


static void
on_timer( timer_callback_info *callback, struct timespec *now ) {
  assert( callback != NULL );
  assert( callback->function != NULL );

  debug( "Executing a timer event ( function = %p, expires_at = %u.%09u, interval = %u.%09u, user_data = %p ).",
         callback->function, callback->expires_at.tv_sec, callback->expires_at.tv_nsec,
         callback->interval.tv_sec, callback->interval.tv_nsec, callback->user_data );

  if ( VALID_TIMESPEC( &callback->expires_at ) ) {
    callback->function( callback->user_data );
    if ( VALID_TIMESPEC( &callback->interval ) ) {
      ADD_TIMESPEC( &callback->expires_at, &callback->interval, &callback->expires_at );
      if ( TIMESPEC_LESS_THEN( &callback->expires_at, now ) ) {
        callback->expires_at.tv_sec = now->tv_sec;
        callback->expires_at.tv_nsec = now->tv_nsec;
      }
    }
    else {
      callback->expires_at.tv_sec = 0;
      callback->expires_at.tv_nsec = 0;
      callback->function = NULL;
    }
    debug( "Set expires_at value to %u.%09u.", callback->expires_at.tv_sec, callback->expires_at.tv_nsec );
  }
  else {
    error( "Invalid expires_at value." );
  }
}


static void
insert_timer_callback( timer_callback_info *new_cb ) {
  assert( timer_callbacks != NULL );
  dlist_element *element, *last = timer_callbacks;
  for ( element = timer_callbacks->next; element != NULL; element = element->next ) {
    timer_callback_info *cb = element->data;
    if ( TIMESPEC_LESS_THEN( &new_cb->expires_at, &cb->expires_at ) ) {
      insert_before_dlist( element, new_cb );
      return;
    }
    last = element;
  }
  insert_after_dlist( last, new_cb );
}


void
_execute_timer_events( int *next_timeout_usec ) {
  assert( next_timeout_usec != NULL );
  struct timespec now;
  timer_callback_info *callback;
  dlist_element *element, *element_next;

  debug( "Executing timer events ( timer_callbacks = %p ).", timer_callbacks );

  assert( clock_gettime( CLOCK_MONOTONIC, &now ) == 0 );
  assert( timer_callbacks != NULL );

  for ( element = timer_callbacks->next; element; element = element_next ) {
    element_next = element->next;
    callback = element->data;
    if ( callback->function != NULL ) {
      if ( TIMESPEC_LESS_THEN( &now, &callback->expires_at ) ) {
        break;
      }
      on_timer( callback, &now );
    }
    delete_dlist_element( element );
    if ( callback->function == NULL ) {
      xfree( callback );
    }
    else {
      insert_timer_callback( callback );
    }
  }

  struct timespec max_timeout = { ( INT_MAX / 1000000 ), 0 };
  struct timespec min_timeout = { 0, 0 };
  if ( timer_callbacks->next == NULL ) {
    TIMESPEC_TO_MICROSECONDS( &max_timeout, next_timeout_usec );
  }
  else {
    callback = timer_callbacks->next->data;
    if ( TIMESPEC_LESS_THEN( &callback->expires_at, &now ) ) {
      TIMESPEC_TO_MICROSECONDS( &min_timeout, next_timeout_usec );
    }
    else {
      struct timespec timeout;
      SUB_TIMESPEC( &callback->expires_at, &now, &timeout );
      if ( TIMESPEC_LESS_THEN( &timeout, &max_timeout ) ) {
        TIMESPEC_TO_MICROSECONDS( &timeout, next_timeout_usec );
      }
      else {
        TIMESPEC_TO_MICROSECONDS( &max_timeout, next_timeout_usec );
      }
    }
  }
}
void ( *execute_timer_events )( int * ) = _execute_timer_events;


bool
_add_timer_event_callback( struct itimerspec *interval, timer_callback callback, void *user_data ) {
  assert( interval != NULL );
  assert( callback != NULL );

  debug( "Adding a timer event callback ( interval = %u.%09u, initial expiration = %u.%09u, callback = %p, user_data = %p ).",
         interval->it_interval.tv_sec, interval->it_interval.tv_nsec,
         interval->it_value.tv_sec, interval->it_value.tv_nsec, callback, user_data );

  timer_callback_info *cb;
  struct timespec now;

  cb = xmalloc( sizeof( timer_callback_info ) );
  memset( cb, 0, sizeof( timer_callback_info ) );
  cb->function = callback;
  cb->user_data = user_data;

  if ( clock_gettime( CLOCK_MONOTONIC, &now ) != 0 ) {
    error( "Failed to retrieve monotonic time ( %s [%d] ).", strerror( errno ), errno );
    xfree( cb );
    return false;
  }

  cb->interval = interval->it_interval;

  if ( VALID_TIMESPEC( &interval->it_value ) ) {
    ADD_TIMESPEC( &now, &interval->it_value, &cb->expires_at );
  }
  else if ( VALID_TIMESPEC( &interval->it_interval ) ) {
    ADD_TIMESPEC( &now, &interval->it_interval, &cb->expires_at );
  }
  else {
    error( "Timer must not be zero when a timer event is added." );
    xfree( cb );
    return false;
  }

  debug( "Set an initial expiration time to %u.%09u.", now.tv_sec, now.tv_nsec );

  assert( timer_callbacks != NULL );
  insert_timer_callback( cb );

  return true;
}
bool ( *add_timer_event_callback )( struct itimerspec *interval, timer_callback callback, void *user_data ) = _add_timer_event_callback;


bool
_add_periodic_event_callback( const time_t seconds, timer_callback callback, void *user_data ) {
  assert( callback != NULL );

  debug( "Adding a periodic event callback ( interval = %u, callback = %p, user_data = %p ).",
         seconds, callback, user_data );

  struct itimerspec interval;

  interval.it_value.tv_sec = 0;
  interval.it_value.tv_nsec = 0;
  interval.it_interval.tv_sec = seconds;
  interval.it_interval.tv_nsec = 0;

  return add_timer_event_callback( &interval, callback, user_data );
}
bool ( *add_periodic_event_callback )( const time_t seconds, timer_callback callback, void *user_data ) = _add_periodic_event_callback;


bool
_delete_timer_event( timer_callback callback, void *user_data ) {
  assert( callback != NULL );

  debug( "Deleting a timer event ( callback = %p, user_data = %p ).", callback, user_data );

  dlist_element *e;

  if ( timer_callbacks == NULL ) {
    debug( "All timer callbacks are already deleted or not created yet." );
    return false;
  }

  for ( e = timer_callbacks->next; e; e = e->next ) {
    timer_callback_info *cb = e->data;
    if ( cb->function == callback && cb->user_data == user_data ) {
      debug( "Deleting a callback ( callback = %p, user_data = %p ).", callback, user_data );
      //xfree( cb );
      //delete_dlist_element( e );
      cb->function = NULL;
      cb->user_data = NULL;
      cb->expires_at.tv_sec = 0;
      cb->expires_at.tv_nsec = 0;
      cb->interval.tv_sec = 0;
      cb->interval.tv_nsec = 0;
      return true;
    }
  }

  error( "No registered timer event callback found." );

  return false;
}
bool ( *delete_timer_event )( timer_callback callback, void *user_data ) = _delete_timer_event;


/*
 * Local variables:
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * End:
 */