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