timer_driver.cpp

Go to the documentation of this file.
00001 #ifndef TIMER_DRIVER_IMPLEMENTATION_FILE
00002 #define TIMER_DRIVER_IMPLEMENTATION_FILE
00003 
00004 /*****************************************************************************\
00005 *                                                                             *
00006 *  Name   : timer_driver                                                      *
00007 *  Author : Chris Koeritz                                                     *
00008 *                                                                             *
00009 *******************************************************************************
00010 * Copyright (c) 2003-$now By Author.  This program is free software; you can  *
00011 * redistribute it and/or modify it under the terms of the GNU General Public  *
00012 * License as published by the Free Software Foundation; either version 2 of   *
00013 * the License or (at your option) any later version.  This is online at:      *
00014 *     http://www.fsf.org/copyleft/gpl.html                                    *
00015 * Please send any updates to: fred@gruntose.com                               *
00016 \*****************************************************************************/
00017 
00018 #include "timer_driver.h"
00019 
00020 #include <basis/function.h>
00021 #include <basis/log_base.h>
00022 #include <basis/mutex.h>
00023 #include <basis/portable.h>
00024 #include <basis/utility.h>
00025 #include <data_struct/amorph.cpp>
00026 #include <data_struct/static_memory_gremlin.h>
00027 #include <mechanisms/ithread.h>
00028 #include <mechanisms/time_stamp.h>
00029 
00030 #include <signal.h>
00031 #include <stdio.h>
00032 #ifdef __UNIX__
00033   #include <sys/time.h>
00034 #endif
00035 
00036 //#define DEBUG_TIMER_DRIVER
00037   // uncomment for noisy code.
00038 
00039 #undef LOG
00040 #define LOG(tpr) printf( (utility::timestamp(true, true) + " timer_driver::" + func + tpr).s() )
00041 
00042 const int INITIAL_TIMER_GRANULARITY = 14;
00043   // the timer will support durations of this length or greater initially.
00044   // later durations will be computed based on the timers waiting.
00045 
00046 const int MAX_TIMER_PREDICTION = 140;
00047   // this is the maximum predictive delay before we wake up again to see if
00048   // any new timed items have arrived.  this helps us to not wait too long
00049   // when something's scheduled in between snoozes.
00050 
00051 const int PAUSE_TIME = 200;
00052   // we will pause this many milliseconds if the timer is already occurring
00053   // when we're trying to get the lock on our list.
00054 
00055 const int LONG_TIME = 1 * HOUR_ms;
00056   // the hook can be postponed a really long time with this when necessary.
00057 
00059 
00060 SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, )
00061 
00062 
00063 
00064 timed_object::~timed_object() {}
00065 
00067 
00068 #ifdef __UNIX__
00069 const int OUR_SIGNAL = SIGUSR2;
00070 
00071 class signalling_thread : public ithread
00072 {
00073 public:
00074   signalling_thread(int initial_interval) : ithread(initial_interval) {}
00075   
00076   void perform_activity(void *formal(ptr)) {
00077     raise(OUR_SIGNAL);
00078   }
00079 
00080 private:
00081 };
00082 #endif
00083 
00084 #ifdef __UNIX__
00085 void timer_driver_private_handler(int signal_seen)
00086 #elif defined(__WIN32__)
00087 void __stdcall timer_driver_private_handler(window_handle hwnd, u_int msg,
00088     UINT_PTR id, u_long time)
00089 #else
00090   #error No timer method known for this OS.
00091 #endif
00092 {
00093   #undef static_class_name
00094   #define static_class_name() "timer_driver"
00095   FUNCDEF("timer_driver_private_handler");
00096 #ifdef __UNIX__
00097   int seen = signal_seen;
00098   if (seen != OUR_SIGNAL) {
00099 #elif defined(__WIN32__)
00100   u_int *seen = (u_int *)id;
00101   if (seen != program_wide_timer().real_timer_id()) {
00102 #else
00103   if (true) {  // unknown OS.
00104 #endif
00105 #ifdef DEBUG_TIMER_DRIVER
00106     LOG(isprintf("unknown signal/message %x caught.", (void *)seen));
00107 #endif
00108     return;
00109   }
00110   program_wide_timer().handle_system_timer();
00111   #undef static_class_name
00112 }
00113 
00115 
00116 class driven_object_record
00117 {
00118 public:
00119   int _duration;  // the interval for timer hits on this object.
00120   timed_object *_to_invoke;  // the object that will be called back.
00121   time_stamp _next_hit;  // next time the timer should hit for this object.
00122   bool _okay_to_invoke;  // true if this object is okay to call timers on.
00123   bool _handling_timer;  // true if we're handling this object right now.
00124 
00125   driven_object_record(int duration, timed_object *to_invoke)
00126   : _duration(duration), _to_invoke(to_invoke), _next_hit(duration),
00127     _okay_to_invoke(true), _handling_timer(false) {}
00128 };
00129 
00130 class driven_objects_list
00131 : public amorph<driven_object_record>, public virtual object_base
00132 {
00133 public:
00134   IMPLEMENT_CLASS_NAME("driven_objects_list");
00135 
00136   int find_obj(timed_object *obj) {
00137     for (int i = 0; i < elements(); i++) {
00138       if (borrow(i) && (borrow(i)->_to_invoke == obj))
00139         return i;
00140     }
00141     return common::NOT_FOUND;
00142   }
00143 };
00144 
00146 
00147 timer_driver::timer_driver()
00148 : _timers(new driven_objects_list),
00149   _lock(new mutex),
00150 #ifdef __UNIX__
00151   _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)),
00152 #endif
00153 #ifdef __WIN32__
00154   _real_timer_id(NIL),
00155 #endif
00156   _in_timer(false)
00157 {
00158   hookup_OS_timer(INITIAL_TIMER_GRANULARITY);
00159 
00160 #ifdef __UNIX__
00161   // register for the our personal signal.
00162   signal(OUR_SIGNAL, &timer_driver_private_handler);
00163   _prompter->start(NIL);
00164 #endif
00165 }
00166 
00167 timer_driver::~timer_driver()
00168 {
00169   FUNCDEF("destructor");
00170 #ifdef __UNIX__
00171   _prompter->stop();
00172 
00173   struct sigaction action;
00174   action.sa_handler = SIG_DFL;
00175   action.sa_sigaction = NIL;
00176   sigemptyset(&action.sa_mask);
00177   action.sa_flags = 0;
00178   action.sa_restorer = NIL;
00179   int ret = sigaction(OUR_SIGNAL, &action, NIL);
00180   if (ret) {
00182   }
00183 #endif
00184   unhook_OS_timer();
00185 
00186   // make sure we aren't still in a timer handler when we reset our list.
00187   while (true) {
00188     _lock->lock();
00189     if (_in_timer) {
00190       _lock->unlock();
00191 #ifdef DEBUG_TIMER_DRIVER
00192       LOG("waiting to acquire timer_driver lock.");
00193 #endif
00194       portable::sleep_ms(PAUSE_TIME);
00195     } else {
00196       break;
00197     }
00198   }
00199 
00200   _timers->reset();  // clear out the registered functions.
00201   _lock->unlock();
00202 
00203   WHACK(_timers);
00204   WHACK(_lock);
00205 #ifdef __UNIX__
00206   WHACK(_prompter);
00207 #endif
00208 
00209 #ifdef DEBUG_TIMER_DRIVER
00210   LOG("timer_driver is closing down.");
00211 #endif
00212 }
00213 
00214 #ifdef __WIN32__
00215 u_int *timer_driver::real_timer_id() { return _real_timer_id; }
00216 #endif
00217 
00218 bool timer_driver::zap_timer(timed_object *to_remove)
00219 {
00220   FUNCDEF("zap_timer");
00221 #ifdef DEBUG_TIMER_DRIVER
00222   if (_in_timer) {
00223     LOG("hmmm: zapping timer while handling previous timer...!");
00224   }
00225 #endif
00226   auto_synchronizer l(*_lock);
00227   int indy = _timers->find_obj(to_remove);
00228   if (negative(indy)) return false;  // unknown.
00229 #ifdef DEBUG_TIMER_DRIVER
00230   LOG(isprintf("zapping timer %x.", to_remove));
00231 #endif
00232   driven_object_record *reco = _timers->borrow(indy);
00233   reco->_okay_to_invoke = false;
00234   if (reco->_handling_timer) {
00235     // results are not guaranteed if we see this situation.
00236 #ifdef DEBUG_TIMER_DRIVER
00237     LOG(isprintf("Logic Error: timer %x being zapped WHILE BEING HANDLED!",
00238         to_remove));
00239 #endif
00240   }
00241   return true;
00242 }
00243 
00244 bool timer_driver::set_timer(int duration, timed_object *to_invoke)
00245 {
00246   FUNCDEF("set_timer");
00247 #ifdef DEBUG_TIMER_DRIVER
00248   if (_in_timer) {
00249     LOG("hmmm: setting timer while handling previous timer...!");
00250   }
00251 #endif
00252 #ifdef DEBUG_TIMER_DRIVER
00253   LOG(isprintf("setting timer %x to %d ms.", to_invoke, duration));
00254 #endif
00255   auto_synchronizer l(*_lock);
00256   // find any existing record.
00257   int indy = _timers->find_obj(to_invoke);
00258   if (negative(indy)) {
00259     // add a new record to list.
00260     _timers->append(new driven_object_record(duration, to_invoke));
00261   } else {
00262     // change the existing record.
00263     driven_object_record *reco = _timers->borrow(indy);
00264     reco->_duration = duration;
00265     reco->_okay_to_invoke = true;  // just in case.
00266   }
00267   return true;
00268 }
00269 
00270 void timer_driver::handle_system_timer()
00271 {
00272   FUNCDEF("handle_system_timer");
00273   if (_in_timer) {
00274 #ifdef DEBUG_TIMER_DRIVER
00275     LOG("terrible error: invoked system timer while handling previous timer.");
00276 #endif
00277     return;
00278   }
00279   unhook_OS_timer();
00280 
00281 #ifdef DEBUG_TIMER_DRIVER
00282   LOG("into handling OS timer...");
00283 #endif
00284 
00285   array<driven_object_record *> to_invoke_now;
00286 
00287   {
00288     // lock the list for a short time, just to put in a stake for the timer
00289     // flag; no one is allowed to change the list while this is set to true.
00290     auto_synchronizer l(*_lock);
00291     _in_timer = true;
00292 
00293     // zip across our list and find out which of the timer functions should be
00294     // invoked.
00295     for (int i = 0; i < _timers->elements(); i++) {
00296       driven_object_record *funky = _timers->borrow(i);
00297       if (!funky) {
00298         const char *msg = "error in timer list logic!";
00299 #ifdef DEBUG_TIMER_DRIVER
00300         LOG(msg);
00301 #endif
00302 #ifdef CATCH_ERRORS
00303         continuable_error(static_class_name(), func, msg);
00304 #endif
00305         _timers->zap(i, i);
00306         i--;  // skip back over dud record.
00307         continue;
00308       }
00309       if (funky->_next_hit <= time_stamp()) {
00310         // this one needs to be jangled.
00311         to_invoke_now += funky;
00312       }
00313     }
00314   }
00315 
00316 #ifdef DEBUG_TIMER_DRIVER
00317   istring pointer_dump;
00318   for (int i = 0; i < to_invoke_now.length(); i++) {
00319     driven_object_record *funky = to_invoke_now[i];
00320     pointer_dump += isprintf("%x ", funky->_to_invoke);
00321   }
00322   if (pointer_dump.t())
00323     LOG(istring("activating ") + pointer_dump);
00324 #endif
00325 
00326   // now that we have a list of timer functions, let's call on them.
00327   for (int i = 0; i < to_invoke_now.length(); i++) {
00328     driven_object_record *funky = to_invoke_now[i];
00329     {
00330       auto_synchronizer l(*_lock);
00331       if (!funky->_okay_to_invoke) continue;  // skip this guy.
00332       funky->_handling_timer = true;
00333     }
00334     // call the timer function.
00335     funky->_to_invoke->handle_timer_callback();
00336     {
00337       auto_synchronizer l(*_lock);
00338       funky->_handling_timer = false;
00339     }
00340     // reset the time for the next hit.
00341     funky->_next_hit.reset(funky->_duration);
00342   }
00343 
00344   // compute the smallest duration before the next guy should fire.
00345   int next_timer_duration = MAX_TIMER_PREDICTION;
00346   time_stamp now;  // pick a point in time as reference for all timers.
00347   for (int i = 0; i < _timers->elements(); i++) {
00348     driven_object_record *funky = _timers->borrow(i);
00349     int funky_time = int(funky->_next_hit.value() - now.value());
00350     // we limit the granularity of timing since we don't want to be raging
00351     // on the CPU with too small a duration.
00352     if (funky_time < INITIAL_TIMER_GRANULARITY)
00353       funky_time = INITIAL_TIMER_GRANULARITY;
00354     if (funky_time < next_timer_duration)
00355       next_timer_duration = funky_time;
00356   }
00357 
00358   {
00359     // release the timer flag again and do any cleanups that are necessary.
00360     auto_synchronizer l(*_lock);
00361     _in_timer = false;
00362     for (int i = 0; i < _timers->elements(); i++) {
00363       driven_object_record *funky = _timers->borrow(i);
00364       if (!funky->_okay_to_invoke) {
00365         // clean up something that was unhooked.
00366         _timers->zap(i, i);
00367         i--;
00368       }
00369     }
00370   }
00371 
00372 #ifdef DEBUG_TIMER_DRIVER
00373   LOG("done handling OS timer.");
00374 #endif
00375 
00376   // set the next expiration time to the smallest next guy.
00377   reset_OS_timer(next_timer_duration);
00378 }
00379 
00380 // the following OS_timer methods do not need to lock the mutex, since they
00381 // are not actually touching the list of timers.
00382 
00383 void timer_driver::hookup_OS_timer(int duration)
00384 {
00385   FUNCDEF("hookup_OS_timer");
00386   if (negative(duration)) {
00387 #ifdef DEBUG_TIMER_DRIVER
00388     LOG("seeing negative duration for timer!");
00389 #endif
00390     duration = 1;
00391   } else if (!duration) {
00392 #ifdef DEBUG_TIMER_DRIVER
00393     LOG("patching zero duration for timer.");
00394 #endif
00395     duration = 1;
00396   }
00397 #ifdef DEBUG_TIMER_DRIVER
00398   LOG(isprintf("hooking next OS timer in %d ms.", duration));
00399 #endif
00400 #ifdef __UNIX__
00401   // just make our thread hit after the duration specified.
00402   _prompter->reschedule(duration);
00403 #elif defined(__WIN32__)
00404   int max_tries_left = 100;
00405   while (max_tries_left-- >= 0) {
00406     _real_timer_id = (u_int *)SetTimer(NIL, 0, duration,
00407         timer_driver_private_handler);
00408     if (!_real_timer_id) {
00409       // failure to set the timer.
00410       LOG("could not set the interval timer.");
00411       portable::sleep_ms(50);  // snooze for a bit to see if we can get right.
00412       continue;
00413     } else
00414       break;  // success hooking timer.
00415   }
00416 #endif
00417 }
00418 
00419 void timer_driver::unhook_OS_timer()
00420 {
00421   FUNCDEF("unhook_OS_timer");
00422 #ifdef __UNIX__
00423   // postpone the thread for quite a while so we can take care of business.
00424   _prompter->reschedule(LONG_TIME);
00425 #elif defined(__WIN32__)
00426   if (_real_timer_id) KillTimer(NIL, (UINT_PTR)_real_timer_id);
00427 #endif
00428 #ifdef DEBUG_TIMER_DRIVER
00429   LOG("unhooked OS timer.");
00430 #endif
00431 }
00432 
00433 void timer_driver::reset_OS_timer(int next_hit)
00434 {
00435   FUNCDEF("reset_OS_timer");
00436   unhook_OS_timer();  // stop the timer from running.
00437   hookup_OS_timer(next_hit);  // restart the timer with the new interval.
00438 }
00439 
00440 
00441 #endif //TIMER_DRIVER_IMPLEMENTATION_FILE
00442 

Generated on Fri Nov 28 04:29:30 2008 for HOOPLE Libraries by  doxygen 1.5.1