updates to move to newer gnu-er time
[feisty_meow.git] / nucleus / library / timely / timer_driver.cpp
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
15 #include <application/windoze_helper.h>
16 #include <basis/functions.h>
17 #include <basis/mutex.h>
18 #include <processes/ethread.h>
19 #include <structures/amorph.h>
20 #include <structures/static_memory_gremlin.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
43 const int INITIAL_TIMER_GRANULARITY = 14;
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
59 //////////////
60
61 SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, )
62
63 //////////////
64
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__)
84 void __stdcall timer_driver_private_handler(window_handle hwnd, basis::un_int msg,
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
113 //////////////
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
145 //////////////
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.
161   signal(OUR_SIGNAL, &timer_driver_private_handler);
162   _prompter->start(NULL_POINTER);
163 #endif
164 }
165
166 timer_driver::~timer_driver()
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) {
184 ///uhhh
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
197       time_control::sleep_ms(PAUSE_TIME);
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
221 bool timer_driver::zap_timer(timeable *to_remove)
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
275 void timer_driver::handle_system_timer()
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 void timer_driver::unhook_OS_timer()
427 {
428 #ifdef DEBUG_TIMER_DRIVER
429   FUNCDEF("unhook_OS_timer");
430 #endif
431 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
432   // postpone the thread for quite a while so we can take care of business.
433   _prompter->reschedule(LONG_TIME);
434 #elif defined(_MSC_VER)
435   if (_real_timer_id) KillTimer(NULL_POINTER, (UINT_PTR)_real_timer_id);
436 #endif
437 #ifdef DEBUG_TIMER_DRIVER
438   LOG("unhooked OS timer.");
439 #endif
440 }
441
442 void timer_driver::reset_OS_timer(int next_hit)
443 {
444 #ifdef DEBUG_TIMER_DRIVER
445   FUNCDEF("reset_OS_timer");
446 #endif
447   unhook_OS_timer();  // stop the timer from running.
448   hookup_OS_timer(next_hit);  // restart the timer with the new interval.
449 }
450
451 } //namespace.
452