feisty meow concerns codebase  2.140
timer_driver.cpp
Go to the documentation of this file.
1 /*
2 * Name : timer_driver
3 * Author : Chris Koeritz
4 
5 * Copyright (c) 2003-$now By Author. This program is free software; you can *
6 * redistribute it and/or modify it under the terms of the GNU General Public *
7 * License as published by the Free Software Foundation; either version 2 of *
8 * the License or (at your option) any later version. This is online at: *
9 * http://www.fsf.org/copyleft/gpl.html *
10 * Please send any updates to: fred@gruntose.com *
11 \*****************************************************************************/
12 
13 #include "timer_driver.h"
14 
16 #include <basis/functions.h>
17 #include <basis/mutex.h>
18 #include <processes/ethread.h>
19 #include <structures/amorph.h>
21 #include <timely/time_control.h>
22 #include <timely/time_stamp.h>
23 
24 #include <signal.h>
25 #include <stdio.h>
26 #ifdef __UNIX__
27  #include <sys/time.h>
28 #endif
29 
30 using namespace basis;
31 using namespace processes;
32 using namespace structures;
33 using namespace timely;
34 
35 #define DEBUG_TIMER_DRIVER
36  // uncomment for noisy code.
37 
38 #undef LOG
39 #define LOG(tpr) printf("%s", (time_stamp::notarize() + "timer_driver::" + func + tpr).s() )
40 
41 namespace timely {
42 
44  // the timer will support durations of this length or greater initially.
45  // later durations will be computed based on the timers waiting.
46 
47 const int MAX_TIMER_PREDICTION = 140;
48  // this is the maximum predictive delay before we wake up again to see if
49  // any new timed items have arrived. this helps us to not wait too long
50  // when something's scheduled in between snoozes.
51 
52 const int PAUSE_TIME = 200;
53  // we will pause this many milliseconds if the timer is already occurring
54  // when we're trying to get the lock on our list.
55 
56 const int LONG_TIME = 1 * HOUR_ms;
57  // the hook can be postponed a really long time with this when necessary.
58 
60 
61 SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, )
62 
63 
65 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
66 const int OUR_SIGNAL = SIGUSR2;
67 
68 class signalling_thread : public ethread
69 {
70 public:
71  signalling_thread(int initial_interval) : ethread(initial_interval) {}
72 
73  void perform_activity(void *formal(ptr)) {
74  raise(OUR_SIGNAL);
75  }
76 
77 private:
78 };
79 #endif
80 
81 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
82 void timer_driver_private_handler(int signal_seen)
83 #elif defined(__WIN32__)
85  UINT_PTR id, un_long time)
86 #else
87  #error No timer method known for this OS.
88 #endif
89 {
90 #ifdef DEBUG_TIMER_DRIVER
91  #undef static_class_name
92  #define static_class_name() "timer_driver"
93  FUNCDEF("timer_driver_private_handler");
94 #endif
95 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
96  int seen = signal_seen;
97  if (seen != OUR_SIGNAL) {
98 #elif defined(__WIN32__)
99  basis::un_int *seen = (basis::un_int *)id;
100  if (seen != program_wide_timer().real_timer_id()) {
101 #else
102  if (true) { // unknown OS.
103 #endif
104 #ifdef DEBUG_TIMER_DRIVER
105  LOG(a_sprintf("unknown signal/message %d caught.", seen));
106 #endif
107  return;
108  }
109  program_wide_timer().handle_system_timer();
110  #undef static_class_name
111 }
112 
114 
115 class driven_object_record
116 {
117 public:
118  int _duration; // the interval for timer hits on this object.
119  timeable *_to_invoke; // the object that will be called back.
120  time_stamp _next_hit; // next time the timer should hit for this object.
121  bool _okay_to_invoke; // true if this object is okay to call timers on.
122  bool _handling_timer; // true if we're handling this object right now.
123 
124  driven_object_record(int duration, timeable *to_invoke)
125  : _duration(duration), _to_invoke(to_invoke), _next_hit(duration),
126  _okay_to_invoke(true), _handling_timer(false) {}
127 };
128 
129 class driven_objects_list
130 : public amorph<driven_object_record>,
131  public virtual root_object
132 {
133 public:
134  DEFINE_CLASS_NAME("driven_objects_list");
135 
136  int find_obj(timeable *obj) {
137  for (int i = 0; i < elements(); i++) {
138  if (borrow(i) && (borrow(i)->_to_invoke == obj))
139  return i;
140  }
141  return common::NOT_FOUND;
142  }
143 };
144 
146 
147 timer_driver::timer_driver()
148 : _timers(new driven_objects_list),
149  _lock(new mutex),
150 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
151  _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)),
152 #else
153  _real_timer_id(NULL_POINTER),
154 #endif
155  _in_timer(false)
156 {
157  hookup_OS_timer(INITIAL_TIMER_GRANULARITY);
158 
159 #ifdef __UNIX__
160  // register for the our personal signal.
162  _prompter->start(NULL_POINTER);
163 #endif
164 }
165 
167 {
168 #ifdef DEBUG_TIMER_DRIVER
169  FUNCDEF("destructor");
170 #endif
171 #ifdef __UNIX__
172  _prompter->stop();
173 
174  struct sigaction action;
175  action.sa_handler = SIG_DFL;
176  action.sa_sigaction = NULL_POINTER;
177  sigemptyset(&action.sa_mask);
178  action.sa_flags = 0;
179 #ifndef __APPLE__
180  action.sa_restorer = NULL_POINTER;
181 #endif
182  int ret = sigaction(OUR_SIGNAL, &action, NULL_POINTER);
183  if (ret) {
185  }
186 #endif
187  unhook_OS_timer();
188 
189  // make sure we aren't still in a timer handler when we reset our list.
190  while (true) {
191  _lock->lock();
192  if (_in_timer) {
193  _lock->unlock();
194 #ifdef DEBUG_TIMER_DRIVER
195  LOG("waiting to acquire timer_driver lock.");
196 #endif
198  } else {
199  break;
200  }
201  }
202 
203  _timers->reset(); // clear out the registered functions.
204  _lock->unlock();
205 
206  WHACK(_timers);
207  WHACK(_lock);
208 #ifdef __UNIX__
209  WHACK(_prompter);
210 #endif
211 
212 #ifdef DEBUG_TIMER_DRIVER
213  LOG("timer_driver is closing down.");
214 #endif
215 }
216 
217 //#ifdef _MSC_VER
218 //basis::un_int *timer_driver::real_timer_id() { return _real_timer_id; }
219 //#endif
220 
222 {
223 #ifdef DEBUG_TIMER_DRIVER
224  FUNCDEF("zap_timer");
225 #endif
226 #ifdef DEBUG_TIMER_DRIVER
227  if (_in_timer) {
228  LOG("hmmm: zapping timer while handling previous timer...!");
229  }
230 #endif
231  auto_synchronizer l(*_lock);
232  int indy = _timers->find_obj(to_remove);
233  if (negative(indy)) return false; // unknown.
234 #ifdef DEBUG_TIMER_DRIVER
235  LOG(a_sprintf("zapping timer %x.", to_remove));
236 #endif
237  driven_object_record *reco = _timers->borrow(indy);
238  reco->_okay_to_invoke = false;
239  if (reco->_handling_timer) {
240  // results are not guaranteed if we see this situation.
241 #ifdef DEBUG_TIMER_DRIVER
242  LOG(a_sprintf("Logic Error: timer %x being zapped WHILE BEING HANDLED!",
243  to_remove));
244 #endif
245  }
246  return true;
247 }
248 
249 bool timer_driver::set_timer(int duration, timeable *to_invoke)
250 {
251 #ifdef DEBUG_TIMER_DRIVER
252  FUNCDEF("set_timer");
253  if (_in_timer) {
254  LOG("hmmm: setting timer while handling previous timer...!");
255  }
256 #endif
257 #ifdef DEBUG_TIMER_DRIVER
258  LOG(a_sprintf("setting timer %x to %d ms.", to_invoke, duration));
259 #endif
260  auto_synchronizer l(*_lock);
261  // find any existing record.
262  int indy = _timers->find_obj(to_invoke);
263  if (negative(indy)) {
264  // add a new record to list.
265  _timers->append(new driven_object_record(duration, to_invoke));
266  } else {
267  // change the existing record.
268  driven_object_record *reco = _timers->borrow(indy);
269  reco->_duration = duration;
270  reco->_okay_to_invoke = true; // just in case.
271  }
272  return true;
273 }
274 
276 {
277 #ifdef DEBUG_TIMER_DRIVER
278  FUNCDEF("handle_system_timer");
279 #endif
280  if (_in_timer) {
281 #ifdef DEBUG_TIMER_DRIVER
282  LOG("terrible error: invoked system timer while handling previous timer.");
283 #endif
284  return;
285  }
286  unhook_OS_timer();
287 
288 #ifdef DEBUG_TIMER_DRIVER
289  LOG("into handling OS timer...");
290 #endif
291 
292  array<driven_object_record *> to_invoke_now;
293 
294  {
295  // lock the list for a short time, just to put in a stake for the timer
296  // flag; no one is allowed to change the list while this is set to true.
297  auto_synchronizer l(*_lock);
298  _in_timer = true;
299 
300  // zip across our list and find out which of the timer functions should be
301  // invoked.
302  for (int i = 0; i < _timers->elements(); i++) {
303  driven_object_record *funky = _timers->borrow(i);
304  if (!funky) {
305  const char *msg = "error: timer_driver's timer list logic is broken.";
306 #ifdef DEBUG_TIMER_DRIVER
307  LOG(msg);
308 #endif
309 #ifdef CATCH_ERRORS
310  throw msg;
311 #endif
312  _timers->zap(i, i);
313  i--; // skip back over dud record.
314  continue;
315  }
316  if (funky->_next_hit <= time_stamp()) {
317  // this one needs to be jangled.
318  to_invoke_now += funky;
319  }
320  }
321  }
322 
323 #ifdef DEBUG_TIMER_DRIVER
324  astring pointer_dump;
325  for (int i = 0; i < to_invoke_now.length(); i++) {
326  driven_object_record *funky = to_invoke_now[i];
327  pointer_dump += a_sprintf("%x ", funky->_to_invoke);
328  }
329  if (pointer_dump.t())
330  LOG(astring("activating ") + pointer_dump);
331 #endif
332 
333  // now that we have a list of timer functions, let's call on them.
334  for (int i = 0; i < to_invoke_now.length(); i++) {
335  driven_object_record *funky = to_invoke_now[i];
336  {
337  auto_synchronizer l(*_lock);
338  if (!funky->_okay_to_invoke) continue; // skip this guy.
339  funky->_handling_timer = true;
340  }
341  // call the timer function.
342  funky->_to_invoke->handle_timer_callback();
343  {
344  auto_synchronizer l(*_lock);
345  funky->_handling_timer = false;
346  }
347  // reset the time for the next hit.
348  funky->_next_hit.reset(funky->_duration);
349  }
350 
351  // compute the smallest duration before the next guy should fire.
352  int next_timer_duration = MAX_TIMER_PREDICTION;
353  time_stamp now; // pick a point in time as reference for all timers.
354  for (int i = 0; i < _timers->elements(); i++) {
355  driven_object_record *funky = _timers->borrow(i);
356  int funky_time = int(funky->_next_hit.value() - now.value());
357  // we limit the granularity of timing since we don't want to be raging
358  // on the CPU with too small a duration.
359  if (funky_time < INITIAL_TIMER_GRANULARITY)
360  funky_time = INITIAL_TIMER_GRANULARITY;
361  if (funky_time < next_timer_duration)
362  next_timer_duration = funky_time;
363  }
364 
365  {
366  // release the timer flag again and do any cleanups that are necessary.
367  auto_synchronizer l(*_lock);
368  _in_timer = false;
369  for (int i = 0; i < _timers->elements(); i++) {
370  driven_object_record *funky = _timers->borrow(i);
371  if (!funky->_okay_to_invoke) {
372  // clean up something that was unhooked.
373  _timers->zap(i, i);
374  i--;
375  }
376  }
377  }
378 
379 #ifdef DEBUG_TIMER_DRIVER
380  LOG("done handling OS timer.");
381 #endif
382 
383  // set the next expiration time to the smallest next guy.
384  reset_OS_timer(next_timer_duration);
385 }
386 
387 // the following OS_timer methods do not need to lock the mutex, since they
388 // are not actually touching the list of timers.
389 
390 void timer_driver::hookup_OS_timer(int duration)
391 {
392  FUNCDEF("hookup_OS_timer");
393  if (negative(duration)) {
394 #ifdef DEBUG_TIMER_DRIVER
395  LOG("seeing negative duration for timer!");
396 #endif
397  duration = 1;
398  } else if (!duration) {
399 #ifdef DEBUG_TIMER_DRIVER
400  LOG("patching zero duration for timer.");
401 #endif
402  duration = 1;
403  }
404 #ifdef DEBUG_TIMER_DRIVER
405  LOG(a_sprintf("hooking next OS timer in %d ms.", duration));
406 #endif
407 //#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
408  // just make our thread hit after the duration specified.
409  _prompter->reschedule(duration);
410 /*#elif defined(_MSC_VER)
411  int max_tries_left = 100;
412  while (max_tries_left-- >= 0) {
413  _real_timer_id = (basis::un_int *)SetTimer(NULL_POINTER, 0, duration,
414  timer_driver_private_handler);
415  if (!_real_timer_id) {
416  // failure to set the timer.
417  LOG("could not set the interval timer.");
418  time_control::sleep_ms(50); // snooze for a bit to see if we can get right.
419  continue;
420  } else
421  break; // success hooking timer.
422  }
423 #endif
424 */
425 }
426 
427 void timer_driver::unhook_OS_timer()
428 {
429 #ifdef DEBUG_TIMER_DRIVER
430  FUNCDEF("unhook_OS_timer");
431 #endif
432 //#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
433  // postpone the thread for quite a while so we can take care of business.
434  _prompter->reschedule(LONG_TIME);
435 //#elif defined(_MSC_VER)
436 // if (_real_timer_id) KillTimer(NULL_POINTER, (UINT_PTR)_real_timer_id);
437 //#endif
438 #ifdef DEBUG_TIMER_DRIVER
439  LOG("unhooked OS timer.");
440 #endif
441 }
442 
443 void timer_driver::reset_OS_timer(int next_hit)
444 {
445 #ifdef DEBUG_TIMER_DRIVER
446  FUNCDEF("reset_OS_timer");
447 #endif
448  unhook_OS_timer(); // stop the timer from running.
449  hookup_OS_timer(next_hit); // restart the timer with the new interval.
450 }
451 
452 } //namespace.
453 
a_sprintf is a specialization of astring that provides printf style support.
Definition: astring.h:440
Represents a sequential, ordered, contiguous collection of objects.
Definition: array.h:54
int length() const
Returns the current reported length of the allocated C array.
Definition: array.h:115
Provides a dynamically resizable ASCII character string.
Definition: astring.h:35
bool t() const
t() is a shortcut for the string being "true", as in non-empty.
Definition: astring.h:97
auto_synchronizer simplifies concurrent code by automatically unlocking.
Definition: mutex.h:113
void lock()
Clamps down on the mutex, if possible.
Definition: mutex.cpp:95
void unlock()
Gives up the possession of the mutex.
Definition: mutex.cpp:107
Provides a platform-independent object for adding threads to a program.
Definition: ethread.h:36
static void sleep_ms(basis::un_int msec)
a system independent name for a forced snooze measured in milliseconds.
Represents a point in time relative to the operating system startup time.
Definition: time_stamp.h:38
timeable is the base for objects that can be hooked into timer events.
Definition: timer_driver.h:33
Provides platform-independent timer support.
Definition: timer_driver.h:51
bool set_timer(int duration, timeable *to_invoke)
sets a timer to call "to_invoke" every "duration" milliseconds.
void handle_system_timer()
invoked by the OS timer support and must be called by main thread.
bool zap_timer(timeable *to_drop)
removes the timer that was established for "to_drop".
#define formal(parameter)
This macro just eats what it's passed; it marks unused formal parameters.
Definition: definitions.h:48
#define NULL_POINTER
The value representing a pointer to nothing.
Definition: definitions.h:32
#define DEFINE_CLASS_NAME(objname)
Defines the name of a class by providing a couple standard methods.
Definition: enhance_cpp.h:45
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition: enhance_cpp.h:57
The guards collection helps in testing preconditions and reporting errors.
Definition: array.h:30
void WHACK(contents *&ptr)
deletion with clearing of the pointer.
Definition: functions.h:121
unsigned long un_long
Abbreviated name for unsigned long integers.
Definition: definitions.h:66
unsigned int un_int
Abbreviated name for unsigned integers.
Definition: definitions.h:62
const int HOUR_ms
Number of milliseconds in an hour.
Definition: definitions.h:122
bool negative(const type &a)
negative returns true if "a" is less than zero.
Definition: functions.h:43
A dynamic container class that holds any kind of object via pointers.
Definition: amorph.h:55
#include <time.h>
Definition: earth_time.cpp:37
const int LONG_TIME
const int MAX_TIMER_PREDICTION
time_locus now()
returns our current locus in the time continuum.
Definition: earth_time.cpp:352
const int PAUSE_TIME
const int INITIAL_TIMER_GRANULARITY
void timer_driver_private_handler(int signal_seen)
const int OUR_SIGNAL
const int PAUSE_TIME
#define SAFE_STATIC(type, func_name, parms)
Statically defines a singleton object whose scope is the program's lifetime.
#define LOG(tpr)
#define program_wide_timer()
provides access to the singleton timer_driver.
Definition: timer_driver.h:108
Aids in achievement of platform independence.
void * window_handle
#define __stdcall