]> git.vomp.tv Git - vompclient.git/blob - timers.cc
New timers code
[vompclient.git] / timers.cc
1 /*\r
2     Copyright 2004-2005 Chris Tallon\r
3 \r
4     This file is part of VOMP.\r
5 \r
6     VOMP is free software; you can redistribute it and/or modify\r
7     it under the terms of the GNU General Public License as published by\r
8     the Free Software Foundation; either version 2 of the License, or\r
9     (at your option) any later version.\r
10 \r
11     VOMP is distributed in the hope that it will be useful,\r
12     but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14     GNU General Public License for more details.\r
15 \r
16     You should have received a copy of the GNU General Public License\r
17     along with VOMP; if not, write to the Free Software\r
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
19 */\r
20 \r
21 #include "timers.h"\r
22 \r
23 Timers* Timers::instance = NULL;\r
24 \r
25 Timers::Timers()\r
26 {\r
27   if (instance) return;\r
28   instance = this;\r
29   initted = false;\r
30 }\r
31 \r
32 Timers::~Timers()\r
33 {\r
34   instance = NULL;\r
35 }\r
36 \r
37 Timers* Timers::getInstance()\r
38 {\r
39   return instance;\r
40 }\r
41 \r
42 int Timers::init()\r
43 {\r
44   if (initted) return 0;\r
45   initted = true;\r
46   logger = Log::getInstance();\r
47 \r
48   logger->log("Timers", Log::DEBUG, "Timers init start");\r
49 \r
50   threadLock(); // lock here, the thread loop will unlock and wait\r
51 logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 1");\r
52   if (!threadStart())\r
53   {\r
54     shutdown();\r
55     return 0;\r
56   }\r
57 \r
58   logger->log("Timers", Log::DEBUG, "Timers init end");\r
59 \r
60   return 1;\r
61 }\r
62 \r
63 int Timers::shutdown()\r
64 {\r
65   if (!initted) return 0;\r
66   initted = false;\r
67 \r
68   logger->log("Timers", Log::DEBUG, "Timers shutdown start");\r
69 \r
70   threadStop();\r
71 \r
72   TimerList::iterator i;\r
73   UINT numTimers = timerList.size();\r
74   while(numTimers)\r
75   {\r
76     i = timerList.begin();\r
77     delete *i;\r
78     timerList.pop_front();\r
79     --numTimers;\r
80   }\r
81 \r
82   logger->log("Timers", Log::DEBUG, "Timers shutdown end");\r
83 \r
84   return 1;\r
85 }\r
86 \r
87 int Timers::setTimer(TimerReceiver* client, int clientReference, time_t requestedTime)\r
88 {\r
89   if (!initted) return 0;\r
90 \r
91   logger->log("Timers", Log::DEBUG, "Starting set timer 1");\r
92 \r
93   Timer* t = new Timer();\r
94   t->client = client;\r
95   t->clientReference = clientReference;\r
96   t->requestedTime.tv_sec = requestedTime;\r
97   t->requestedTime.tv_nsec = 0;\r
98 \r
99 logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 2");\r
100   threadLock();\r
101 logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 2");\r
102   timerList.push_back(t);\r
103   resetThreadFlag = true;\r
104   threadSignalNoLock();\r
105 logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 2");\r
106   threadUnlock();\r
107 \r
108   logger->log("Timers", Log::DEBUG, "1 Have set timer for %p ref %i", client, clientReference);\r
109 \r
110   return 1;\r
111 }\r
112 \r
113 int Timers::setTimer(TimerReceiver* client, int clientReference, struct timespec duration)\r
114 {\r
115   if (!initted) return 0;\r
116 \r
117   logger->log("Timers", Log::DEBUG, "Starting set timer 2");\r
118 \r
119   Timer* t = new Timer();\r
120   t->client = client;\r
121   t->clientReference = clientReference;\r
122 \r
123   struct timespec currentTime;\r
124   clock_gettime(CLOCK_REALTIME, &currentTime);\r
125 \r
126   t->requestedTime.tv_sec = currentTime.tv_sec + duration.tv_sec;\r
127   t->requestedTime.tv_nsec = currentTime.tv_nsec + duration.tv_nsec;\r
128   if (t->requestedTime.tv_nsec > 999999999)\r
129   {\r
130     ++t->requestedTime.tv_sec;\r
131     t->requestedTime.tv_nsec -= 1000000000;\r
132     logger->log("Timers", Log::DEBUG, "Second rollover - CHECK FIXME");\r
133   }\r
134 \r
135 logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 3");\r
136   threadLock();\r
137 logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 3");\r
138   timerList.push_back(t);\r
139   resetThreadFlag = true;\r
140   threadSignalNoLock();\r
141 logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 3");\r
142   threadUnlock();\r
143 \r
144   logger->log("Timers", Log::DEBUG, "2 Have set timer for %p ref %i", client, clientReference);\r
145 \r
146   return 1;\r
147 }\r
148 \r
149 int Timers::cancelTimer(TimerReceiver* client, int clientReference)\r
150 {\r
151   if (!initted) return 0;\r
152 \r
153   logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());\r
154 \r
155 logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 4");\r
156   threadLock();\r
157 logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 4");\r
158   TimerList::iterator i;\r
159   Timer* currentTimer = NULL;\r
160   for(i = timerList.begin(); i != timerList.end(); i++)\r
161   {\r
162     currentTimer = *i;\r
163     logger->log("Timers", Log::DEBUG, "I: %p %i : %p %i", client, clientReference, currentTimer->client, currentTimer->clientReference);\r
164     if ((currentTimer->client == client) && (currentTimer->clientReference == clientReference))\r
165     {\r
166       timerList.erase(i);\r
167       logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);\r
168       break;\r
169       // At this point currentTimer is not in the list but might still be nextTimer in the thread\r
170     }\r
171   }\r
172   if (i == timerList.end())\r
173   {\r
174     // no timer found\r
175     logger->log("Timers", Log::DEBUG, "No timer found in cancelTimer %p ref %i", client, clientReference);\r
176 logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 4");\r
177     threadUnlock();\r
178     return 0;\r
179   }\r
180 \r
181   resetThreadFlag = true;\r
182   threadSignalNoLock();\r
183 logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 4");\r
184   threadUnlock();\r
185 \r
186 \r
187   return 1;\r
188 }\r
189 \r
190 void Timers::threadMethod()\r
191 {\r
192   struct timespec nextTime;\r
193   Timer* nextTimer = NULL;\r
194   resetThreadFlag = true;\r
195 \r
196   // locked here\r
197 \r
198   while(1)\r
199   {\r
200     if (resetThreadFlag)\r
201     {\r
202       resetThreadFlag = false;\r
203 \r
204       // Work out the next Timer\r
205 \r
206       nextTime.tv_sec = 0;\r
207       nextTime.tv_nsec = 0;\r
208       nextTimer = NULL;\r
209 \r
210       TimerList::iterator i;\r
211       Timer* currentTimer = NULL;\r
212       for(i = timerList.begin(); i != timerList.end(); i++)\r
213       {\r
214         currentTimer = *i;\r
215         if (!nextTimer)\r
216         {\r
217           nextTime.tv_sec = currentTimer->requestedTime.tv_sec;\r
218           nextTime.tv_nsec = currentTimer->requestedTime.tv_nsec;\r
219           nextTimer = currentTimer;\r
220         }\r
221         else\r
222         {\r
223           if (currentTimer->requestedTime.tv_sec < nextTime.tv_sec)\r
224           {\r
225             nextTime.tv_sec = currentTimer->requestedTime.tv_sec;\r
226             nextTime.tv_nsec = currentTimer->requestedTime.tv_nsec;\r
227             nextTimer = currentTimer;\r
228           }\r
229           else if (currentTimer->requestedTime.tv_sec == nextTime.tv_sec)\r
230           {\r
231             if (currentTimer->requestedTime.tv_nsec < nextTime.tv_nsec)\r
232             {\r
233               nextTime.tv_sec = currentTimer->requestedTime.tv_sec;\r
234               nextTime.tv_nsec = currentTimer->requestedTime.tv_nsec;\r
235               nextTimer = currentTimer;\r
236             }\r
237           }\r
238         }\r
239       }\r
240     }\r
241 \r
242     if (nextTimer)\r
243     {\r
244       logger->log("Timers", Log::DEBUG, "List size: %i. nextTimer: %p. nextTime.tv_sec: %li. nextTime.tv_nsec: %li", timerList.size(), nextTimer, nextTime.tv_sec, nextTime.tv_nsec);\r
245 \r
246 \r
247 logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX (1)");\r
248       threadWaitForSignalTimed(&nextTime); // FIXME does this work if the time is in the past?\r
249 logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 5");\r
250       logger->log("Timers", Log::DEBUG, "FIXME CHECK does waitforsignaltimed work for time < now?");\r
251 \r
252       // unlocks in the wait\r
253     }\r
254     else\r
255     {\r
256 logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX (2)");\r
257       threadWaitForSignal();\r
258 logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 6");\r
259       // unlocks in the wait\r
260     }\r
261 \r
262     // ok. we have been signalled or the time has run out\r
263     // This only gets signalled if it is to reset or die\r
264 \r
265     // First check for die..\r
266     threadCheckExit(); // exiting thread with mutex locked\r
267 \r
268     // Check for reset..\r
269     // This can be caused by an addition or deletion to the list\r
270     if (resetThreadFlag) continue;\r
271 \r
272     // timer ran out\r
273 \r
274     Log::getInstance()->log("Timers", Log::DEBUG, "Timer firing for client %p ref %i", nextTimer->client, nextTimer->clientReference);\r
275 \r
276     // send this timer to the timer receiver, via the command message queue\r
277     // so that the gui mutex is locked when it happens\r
278 \r
279     Message* m = new Message();\r
280     m->from = this;\r
281     m->to = nextTimer->client;\r
282     m->message = Message::TIMER;\r
283     m->parameter = nextTimer->clientReference;\r
284 \r
285     if (!Command::getInstance()->postMessageIfNotBusy(m))\r
286     {\r
287       // GUI mutex was locked\r
288       // abort this timer delivery - it might be trying to be deleted!\r
289       delete m;\r
290 \r
291       // now unlock the timers mutex for a fraction of a second\r
292       // in case the gui thread is waiting on the timers mutex\r
293       threadUnlock();\r
294 logger->log("Timers", Log::DEBUG, "un-LOCKED -TIMERS- MUTEX (3)");\r
295       printf("\n\n\n WOOOOO \n\n\n The anti deadlock code is working!!! \n\n\n");\r
296       usleep(10000); // 5ms - too long?\r
297 logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 7");\r
298       threadLock();\r
299 logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 7");\r
300       resetThreadFlag = true;\r
301     }\r
302     else\r
303     {\r
304       // timer was delivered\r
305       timerList.remove(nextTimer);\r
306       delete nextTimer;\r
307       nextTimer = NULL;\r
308       resetThreadFlag = true;\r
309     }\r
310   }\r
311 }\r
312 \r
313 /*\r
314 \r
315 Avoiding deadlock using the timer class...\r
316 \r
317 Situation:\r
318 \r
319 timer condwait finishes\r
320 timers is about to fire a timer\r
321 timers locks timers-mutex\r
322 \r
323     user presses a button\r
324     command locks gui-mutex\r
325 \r
326 timers tries to get gui-mutex\r
327 \r
328     view receives button\r
329     view wants to delete itself\r
330     view tries to deletetimer\r
331     goes into delete timer\r
332     waits on timers mutex\r
333 \r
334 - deadlock\r
335 \r
336 \r
337 Solution:\r
338 \r
339 timers tries to get gui mutex\r
340 if mutex is locked already abort\r
341 unlock timers mutex\r
342 wait a fraction of time\r
343 (allow other thread to lock timers mutex)\r
344 lock timers mutex\r
345 set reset flag to recalculate\r
346 - if timer has been cancelled next timer will be calced\r
347 - if timer has not been cancelled it will be called next\r
348 \r
349 */\r