]> git.vomp.tv Git - vompclient.git/blob - timers.cc
b9bb844833c81616d64847e2b748aa8ecb3b925a
[vompclient.git] / timers.cc
1 /*
2     Copyright 2004-2007 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21 #include "timers.h"
22
23 #include "log.h"
24 #include "command.h"
25 #include "timerreceiver.h"
26
27 Timers* Timers::instance = NULL;
28
29 Timers::Timers()
30 {
31   if (instance) return;
32   instance = this;
33   initted = false;
34 }
35
36 Timers::~Timers()
37 {
38   instance = NULL;
39 }
40
41 Timers* Timers::getInstance()
42 {
43   return instance;
44 }
45
46 int Timers::init()
47 {
48   if (initted) return 0;
49   initted = true;
50   logger = Log::getInstance();
51
52   if (!threadStart())
53   {
54     shutdown();
55     return 0;
56   }
57
58   return 1;
59 }
60
61 int Timers::shutdown()
62 {
63   if (!initted) return 0;
64   initted = false;
65
66   logger->log("Timers", Log::DEBUG, "Timers shutdown start");
67
68   threadStop();
69
70   TimerEvent* timerEvent = NULL;
71   TimerReceiver* client = NULL;
72   int clientReference = 0;
73
74   while(timerList.size())
75   {
76     threadLock();
77     timerEvent = timerList.front();
78     client = timerEvent->client;
79     clientReference = timerEvent->clientReference;
80     threadUnlock();
81
82     cancelTimer(client, clientReference);
83   }
84
85   logger->log("Timers", Log::DEBUG, "Timers shutdown end");
86
87   return 1;
88 }
89
90 bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSEC)
91 {
92   if (!initted) return 0;
93
94   logger->log("Timers", Log::DEBUG, "Starting set timer 1");
95
96   //logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 2");
97   threadLock();
98
99   // Check that this timer is not already in the list
100   TimerList::iterator i;
101   TimerEvent* currentTimerEvent = NULL;
102   for(i = timerList.begin(); i != timerList.end(); i++)
103   {
104     currentTimerEvent = *i;
105
106     if ((currentTimerEvent->client == client) && (currentTimerEvent->clientReference == clientReference))
107     {
108       // Timer exists already, either waiting or running
109       // Update the clocks
110       currentTimerEvent->requestedTime.tv_sec = requestedTime;
111       currentTimerEvent->requestedTime.tv_nsec = requestedTimeNSEC;
112
113       if (currentTimerEvent->running)
114       {
115         // If this timerEvent is currently running, update the clocks and set the restart flag.
116         // Instead of being deleted in timerEventFinished it will be restarted
117         currentTimerEvent->restartAfterFinish = true;
118         // Don't need to resetThreadFlag because this timer isn't re-live yet
119         threadUnlock();
120         return true;
121       }
122       else
123       {
124         // A waiting timer has been edited
125         resetThreadFlag = true;
126         threadSignalNoLock();
127         threadUnlock();
128         return true;
129       }
130     }
131   }
132
133   // Timer did not exist already
134
135   TimerEvent* t = new TimerEvent();
136   t->client = client;
137   t->clientReference = clientReference;
138   t->requestedTime.tv_sec = requestedTime;
139   t->requestedTime.tv_nsec = requestedTimeNSEC;
140
141   //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 2");
142   timerList.push_back(t);
143   resetThreadFlag = true;
144   threadSignalNoLock();
145   //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 2");
146   threadUnlock();
147
148   logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference);
149
150   return true;
151 }
152
153 bool Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs)
154 {
155   struct timespec currentTime;
156
157 #ifndef WIN32
158   clock_gettime(CLOCK_REALTIME, &currentTime);
159 #else
160   SYSTEMTIME systime;
161   __int64  filetime;
162   __int64  test;
163   GetSystemTime(&systime);
164   SystemTimeToFileTime(&systime,(FILETIME*)&filetime);
165    currentTime.tv_sec=(filetime-WINDOWS_TIME_BASE_OFFSET)/(10*1000*1000);
166    //#error "Hier gibt was zu tun!"
167    currentTime.tv_nsec=((filetime-WINDOWS_TIME_BASE_OFFSET)%(10*1000*1000))*100;
168 #endif
169
170   long int requestedTime;
171   long int requestedTimeNSEC;
172
173   requestedTime = currentTime.tv_sec + requestedSecs;
174   requestedTimeNSEC = currentTime.tv_nsec + requestedNSecs;
175   if (requestedTimeNSEC > 999999999)
176   {
177     ++requestedTime;
178     requestedTimeNSEC -= 1000000000;
179     logger->log("Timers", Log::DEBUG, "Second rollover - CHECK FIXME");
180   }
181
182   return setTimerT(client, clientReference, requestedTime, requestedTimeNSEC);
183 }
184
185 bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
186 {
187   /* This method locks the timers mutex
188      Then one of three things can happen:
189      1. The TimerEvent is found, running = false. This means it hasn't started yet.
190         Delete the timer normally, set resetFlag
191      2. The TimerEvent is found, running = true. This means the timer is currently firing,
192         timercall on the client is being called.
193         a. Thread calling cancelTimer is an external thread: In this case, this thread
194            calling cancelTimer needs to unlock and wait for the timercall thread to get
195            back. (sleeps or signalling)
196         b. the timercall thread is calling cancelTimer. remove any restartAfterFinished
197            request, but otherwise ignore the request to cancelTimer because it has already
198            fired. The timercall thread will return to the calling code and eventually
199            terminate in threadEventFinished.
200      3. The TimerEvent is not found. Client error or the thread returned to
201         the Timers module in between client calling cancelTimer and cancelTimer actually
202         running. Do nothing, return normally.
203
204   By making sure there is no waiting timerevent, and no running timerevent, this ensures
205   that the program cannot segfault because a timer fired on a just deleted object.
206
207   */
208
209   if (!initted) return false;
210
211   logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());
212
213   while(1)
214   {
215     threadLock();
216
217     TimerList::iterator i;
218     TimerEvent* currentTimerEvent = NULL;
219     for(i = timerList.begin(); i != timerList.end(); i++)
220     {
221       currentTimerEvent = *i;
222       if ((currentTimerEvent->client == client) && (currentTimerEvent->clientReference == clientReference))
223       {
224         break;
225       }
226     }
227
228     if (i == timerList.end())
229     {
230       // Case 3, no timer found
231       threadUnlock();
232       return true;
233     }
234     else
235     {
236       // Timer found, Case 1 or 2
237
238       if (currentTimerEvent->running == false)
239       {
240         // Case 1. Just delete the timer and reset the thread.
241         timerList.erase(i);
242         delete currentTimerEvent;
243         logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
244         resetThreadFlag = true;
245         threadSignalNoLock();
246         threadUnlock();
247         return true;
248       }
249       else
250       {
251         if (Thread_TYPE::thisThreadID() == currentTimerEvent->getThreadID())
252         {
253           // Case 2 b.
254           // The thread requesting cancelTimer is the timer thread itself, the timer has already fired.
255           logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer itself calling - ignore", client, clientReference);
256           currentTimerEvent->restartAfterFinish = false; // in case a restart had already been set.
257           threadUnlock();
258           return true;
259         }
260
261         // Case 2 a. For now, use polling with a 50ms delay.
262         // Don't delete a running timer.
263         // FIXME upgrade me to signalling
264
265         logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference);
266
267         threadUnlock();
268         MILLISLEEP(50);
269       }
270     }
271   } // end of the big while loop
272 }
273
274 void Timers::timerEventFinished(TimerEvent* timerEvent)
275 {
276   // This function takes out the already timercall'd TimerEvent from the list
277   // Or resets it if restart flag is true
278
279
280   if (!initted) return;
281   threadLock();
282
283   logger->log("Timers", Log::DEBUG, "timerEventFinished for %p", timerEvent->client);
284
285   for(TimerList::iterator i = timerList.begin(); i != timerList.end(); i++)
286   {
287     if (timerEvent != *i) continue;
288
289     if (timerEvent->restartAfterFinish)
290     {
291       logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p", timerEvent->client);
292
293       timerEvent->restartAfterFinish = false;
294       timerEvent->running = false;
295       resetThreadFlag = true;
296       threadSignalNoLock();
297     }
298     else
299     {
300       // The removal of a called and non-restart TimerEvent doesn't need the threadMethod to be reset
301       timerList.erase(i);
302       logger->log("Timers", Log::DEBUG, "timerEventFinished for %p %i - remove done", timerEvent->client, timerEvent->clientReference);
303       delete timerEvent;
304     }
305
306     break;
307   }
308   // FIXME At this point, this should signal all threads waiting on cancelTimer
309   threadUnlock();
310
311   // Kill this thread, as it's the one started for the timer event
312   Thread_TYPE::threadSuicide();
313 }
314
315 void Timers::threadMethod()
316 {
317   struct timespec nextTime;
318   TimerEvent* nextTimer = NULL;
319   resetThreadFlag = true;
320
321   threadLock();
322
323   while(1)
324   {
325     if (resetThreadFlag)
326     {
327       resetThreadFlag = false;
328
329       // Work out the next Timer
330
331       nextTime.tv_sec = 0;
332       nextTime.tv_nsec = 0;
333       nextTimer = NULL;
334
335       TimerList::iterator i;
336       TimerEvent* currentTimer = NULL;
337       for(i = timerList.begin(); i != timerList.end(); i++)
338       {
339         currentTimer = *i;
340         if (currentTimer->running) continue; // has already been timercall'd
341
342         if (!nextTimer)
343         {
344           nextTime.tv_sec = currentTimer->requestedTime.tv_sec;
345           nextTime.tv_nsec = currentTimer->requestedTime.tv_nsec;
346           nextTimer = currentTimer;
347         }
348         else
349         {
350           if (currentTimer->requestedTime.tv_sec < nextTime.tv_sec)
351           {
352             nextTime.tv_sec = currentTimer->requestedTime.tv_sec;
353             nextTime.tv_nsec = currentTimer->requestedTime.tv_nsec;
354             nextTimer = currentTimer;
355           }
356           else if (currentTimer->requestedTime.tv_sec == nextTime.tv_sec)
357           {
358             if (currentTimer->requestedTime.tv_nsec < nextTime.tv_nsec)
359             {
360               nextTime.tv_sec = currentTimer->requestedTime.tv_sec;
361               nextTime.tv_nsec = currentTimer->requestedTime.tv_nsec;
362               nextTimer = currentTimer;
363             }
364           }
365         }
366       }
367     }
368
369     if (nextTimer)
370     {
371 //##      logger->log("Timers", Log::DEBUG, "List size: %i. nextTimerClient: %p/%i. nextTime.tv_sec: %li. nextTime.tv_nsec: %li", timerList.size(), nextTimer->client, nextTimer->clientReference, nextTime.tv_sec, nextTime.tv_nsec);
372
373
374       //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX (1)");
375       threadWaitForSignalTimed(&nextTime);
376       //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 5");
377
378       // unlocks in the wait
379     }
380     else
381     {
382       //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX (2)");
383       threadWaitForSignal();
384       //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 6");
385       // unlocks in the wait
386     }
387
388     // Mutex locked again here by exit of wait or timedwait above
389
390     // ok. we have been signalled or the time has run out
391     // This only gets signalled if it is to reset or die
392
393     // First check for die..
394     threadCheckExit(); // exiting thread with mutex locked
395
396     // Check for reset..
397     // This can be caused by an addition or deletion to the list
398     if (resetThreadFlag || (nextTimer == NULL)) continue;
399
400     // timer ran out
401
402     Log::getInstance()->log("Timers", Log::DEBUG, "Timer firing for client %p ref %i", nextTimer->client, nextTimer->clientReference);
403
404     nextTimer->run(); // sets timerevent to running and starts it
405     resetThreadFlag = true; // find a new timer to wait on
406   }
407 }
408
409
410
411 // Class TimerEvent
412
413 TimerEvent::TimerEvent()
414 {
415   running = false;
416   restartAfterFinish = false;
417   client = NULL;
418   clientReference = 0;
419   requestedTime.tv_sec = 0;
420   requestedTime.tv_nsec = 0;
421 }
422
423 void TimerEvent::threadMethod()
424 {
425   Log::getInstance()->log("Timers", Log::DEBUG, "sending timer to %p with parameter %u", client, clientReference);
426   client->timercall(clientReference);
427   Timers::getInstance()->timerEventFinished(this); // does not return
428 }
429
430 void TimerEvent::run()
431 {
432   running = true;
433   threadStart();
434 }