timer_driver.cpp

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