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
30using namespace basis;
31using namespace processes;
32using namespace structures;
33using namespace timely;
34
35#define DEBUG_TIMER_DRIVER
36 // uncomment for noisy code.
37
38#undef LOG
39#define LOG(tpr) printf("%s\n", (time_stamp::notarize() + "timer_driver::" + func + ": " + tpr).s() )
40
41namespace 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
47const 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
52const 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
56const int LONG_TIME = 1 * HOUR_ms;
57 // the hook can be postponed a really long time with this when necessary.
58
60
62
63
64
65#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
66const int OUR_SIGNAL = SIGUSR2;
67
68class signalling_thread : public ethread
69{
70public:
71 signalling_thread(int initial_interval) : ethread(initial_interval) {}
72
73 void perform_activity(void *formal(ptr)) {
74 raise(OUR_SIGNAL);
75 }
76
77private:
78};
79#endif
80
81#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
82void 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
115class driven_object_record
116{
117public:
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
129class driven_objects_list
130: public amorph<driven_object_record>,
131 public virtual root_object
132{
133public:
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
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
249bool 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
390void 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
427void timer_driver::unhook_OS_timer()
428{
429//#ifdef DEBUG_TIMER_DRIVER
430// FUNCDEF("unhook_OS_timer");
431//#endif
432
433//#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
434 // postpone the thread for quite a while so we can take care of business.
435 _prompter->reschedule(LONG_TIME);
436//#elif defined(_MSC_VER)
437// if (_real_timer_id) KillTimer(NULL_POINTER, (UINT_PTR)_real_timer_id);
438//#endif
439
440//#ifdef DEBUG_TIMER_DRIVER
441// LOG("unhooked OS timer.");
442//#endif
443}
444
445void timer_driver::reset_OS_timer(int next_hit)
446{
447#ifdef DEBUG_TIMER_DRIVER
448 FUNCDEF("reset_OS_timer");
449#endif
450 unhook_OS_timer(); // stop the timer from running.
451 hookup_OS_timer(next_hit); // restart the timer with the new interval.
452}
453
454} //namespace.
455
#define LOG(s)
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:101
void unlock()
Gives up the possession of the mutex.
Definition mutex.cpp:113
Provides a platform-independent object for adding threads to a program.
Definition ethread.h:36
ethread()
creates a single-shot thread object.
Definition ethread.cpp:86
int elements() const
the maximum number of elements currently allowed in this amorph.
Definition amorph.h:66
driven_object_record * borrow(int field)
Returns a pointer to the information at the index "field".
Definition amorph.h:448
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.
Provides platform-independent timer support.
bool set_timer(int duration, timeable *to_invoke)
sets a timer to call "to_invoke" every "duration" milliseconds.
static timer_driver & global_timer_driver()
the first time this is invoked, it creates a program-wide timer driver.
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:42
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition enhance_cpp.h:54
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.
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>
const int LONG_TIME
const int MAX_TIMER_PREDICTION
time_locus now()
returns our current locus in the time continuum.
const int PAUSE_TIME
const int INITIAL_TIMER_GRANULARITY
void timer_driver_private_handler(int signal_seen)
const int OUR_SIGNAL
#define SAFE_STATIC(type, func_name, parms)
Statically defines a singleton object whose scope is the program's lifetime.
#define program_wide_timer()
provides access to the singleton timer_driver.
Aids in achievement of platform independence.
void * window_handle
#define __stdcall