]> git.vomp.tv Git - vompclient.git/blob - timers.cc
Rename TCP class to TCPOld
[vompclient.git] / timers.cc
1 /*
2     Copyright 2004-2020 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, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "log.h"
21
22 #include "timers.h"
23
24 Timers* Timers::instance = NULL;
25
26 Timers::Timers()
27 {
28   if (instance) return;
29   instance = this;
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 false;
45   logger = Log::getInstance();
46
47   timersMutex.lock(); // Start thread with mutex locked
48   recalc = true;
49   timersThread = std::thread([this] { masterLoop(); });
50   logger->log("Timers", Log::DEBUG, "Timers started");
51   initted = true;
52   return true;
53 }
54
55 void Timers::shutdown()
56 {
57   if (!initted) return;
58
59   std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock the mutex
60   // main loop is in cond.wait/_until
61
62   // stop the main thread
63   quitThread = true;  // main loop effectively dead, but join it later
64
65   // Remove any future timer events from the list
66   for(auto i = timerList.begin(); i != timerList.end(); )
67   {
68     TimerEvent* te = *i;
69
70     if (!te->running)
71     {
72       delete te;
73       i = timerList.erase(i);
74     }
75     else
76       ++i;
77   }
78
79   // Now wait for all timer event threads to end
80   while(timerList.size())
81   {
82     timersCond.wait(lockWrapper); // unlocks in wait. waiting for reap signals
83     // locked
84     // Assume there has been a reap signal. Could be spurious but no harm done
85     reap();
86     timersCond.notify_all(); // In case of waiting cancelTimers
87   }
88
89   timersCond.notify_one(); // In case there were no TimerEvents to reap, wake the masterLoop thread
90   lockWrapper.unlock(); // main loop exits if it hasn't already
91   timersThread.join();
92 }
93
94 bool Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs)
95 {
96   std::chrono::system_clock::time_point fireTime = std::chrono::system_clock::now();
97   fireTime += std::chrono::seconds(requestedSecs);
98   fireTime += std::chrono::nanoseconds(requestedNSecs);
99   return setTimerC(client, clientReference, fireTime);
100 }
101
102 bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSecs)
103 {
104   std::chrono::system_clock::time_point fireTime = std::chrono::system_clock::from_time_t(requestedTime);
105   fireTime += std::chrono::nanoseconds(requestedTimeNSecs);
106   return setTimerC(client, clientReference, fireTime);
107 }
108
109 bool Timers::setTimerC(TimerReceiver* client, int clientReference, std::chrono::system_clock::time_point& fireTime)
110 {
111   if (!initted) return 0;
112
113   logger->log("Timers", Log::DEBUG, "Starting set timer chrono");
114
115   std::lock_guard<std::mutex> lock(timersMutex);
116
117   // Check that this timer is not already in the list
118   for(auto foundTimerEvent : timerList)
119   {
120     if ((foundTimerEvent->client == client) && (foundTimerEvent->clientReference == clientReference))
121     {
122       // Timer exists already, either waiting or running
123       // Update requestedTime
124       foundTimerEvent->requestedTime = fireTime;
125
126       if (foundTimerEvent->running)
127       {
128         // If this timerEvent is currently running, update the clocks and set the restart flag.
129         // Instead of being deleted in timerEventFinished it will be restarted
130         foundTimerEvent->restartAfterFinish = true;
131         // Don't need to resetThreadFlag because this timer isn't re-live yet
132         return true; // unlock
133       }
134       else
135       {
136         logger->log("Timers", Log::DEBUG, "Editing existing timer");
137
138         // A waiting timer has been edited
139         recalc = true;
140         timersCond.notify_all();
141         return true; // unlock
142       }
143     }
144   }
145
146   // Timer did not exist already
147
148   TimerEvent* t = new TimerEvent();
149   t->client = client;
150   t->clientReference = clientReference;
151   t->requestedTime = fireTime;
152
153   timerList.push_back(t);
154   recalc = true;
155   timersCond.notify_all();
156   logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference);
157   return true; // unlock
158 }
159
160 void Timers::masterLoop()
161 {
162   // mutex is locked already
163   std::unique_lock<std::mutex> lockWrapper(timersMutex, std::defer_lock);
164
165   TimerEvent* nextTimer = NULL;
166
167   while(1)
168   {
169     if (recalc)
170     {
171       // work out the next fire time
172       nextTimer = NULL;
173
174       for(TimerEvent* thisTimer : timerList)
175       {
176         if (thisTimer->running) continue; // has already been timercall'd
177
178         if (!nextTimer)
179         {
180           nextTimer = thisTimer;
181         }
182         else
183         {
184           if (thisTimer->requestedTime < nextTimer->requestedTime)
185           {
186             nextTimer = thisTimer;
187           }
188         }
189       }
190
191       recalc = false;
192     }
193
194     std::cv_status cvs;
195     if (nextTimer)
196     {
197       // wait for signal timed
198       cvs = timersCond.wait_until(lockWrapper, nextTimer->requestedTime); //unlocks in wait
199     }
200     else
201     {
202       // wait for signal
203       timersCond.wait(lockWrapper); //unlocks in wait
204     }
205
206     // and we're back - we've been signalled (quit, recalc, reap) or the time ran out, or spurious. mutex locked.
207
208     // quit? -> quit
209     if (quitThread) return; // unlocks motex
210
211     if (doReap)
212     {
213       reap();
214       timersCond.notify_all(); // in case of waiting cancelTimers
215     }
216
217     // recalc? -> restart loop
218     if (recalc) continue;
219
220     // time ran out? -> fire a timer
221     if (nextTimer && (cvs == std::cv_status::timeout))
222     {
223       nextTimer->run();
224       recalc = true;
225     }
226 //  else
227 //  {
228       // not quit, not recalc, and either:
229       // 1. there is no next timer
230       // 2. there is a next timer but the timeout didn't expire
231       // therefore, this is a spurious wakeup. Leave recalc == false, go around, and do wait/wait_until again.
232 //    continue;
233 //  }
234   }
235
236   // If you want to do any cleanup after the main loop, change the quitthread return to break and put cleanup here. mutex will be locked
237 }
238
239 void Timers::reap() // Master timers thread, mutex locked (or shutdown, mutex locked)
240 {
241   for(auto i = timerList.begin(); i != timerList.end(); )
242   {
243     TimerEvent* te = *i;
244
245     if (te->completed)
246     {
247       te->timerThread.join();
248
249       if (te->restartAfterFinish)
250       {
251         logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p %i", te->client, te->clientReference);
252         te->restartAfterFinish = false;
253         te->running = false;
254         te->completed = false;
255         recalc = true;
256         ++i;
257       }
258       else
259       {
260         delete te;
261         i = timerList.erase(i);
262       }
263     }
264     else
265       ++i;
266   }
267 }
268
269 void Timers::reapTimerEvent(TimerEvent* te) // Called by a TimerEvent thread
270 {
271   std::lock_guard<std::mutex> lock(timersMutex);
272   te->completed = true;
273   doReap = true;
274   timersCond.notify_all(); // Would be notify_one, but in the case of shutdown the thread within shutdown() will be
275   // handling the reaping. The notify that goes to the masterLoop will cause it to return from wait and sit waiting
276   // for the lock so it can examine quitThread and return. Doesn't matter which thread is unblocked first
277 }
278
279 bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
280 {
281   /* This method locks the timers mutex
282      Then one of three things can happen:
283      1. The TimerEvent is found, running = false. This means it hasn't started yet.
284         Delete the timer normally, set recalc
285      2. The TimerEvent is found, running = true. This means the timer is currently firing,
286         timercall on the client is being called.
287         a. Thread calling cancelTimer is an external thread: In this case, this thread
288            calling cancelTimer needs to unlock and wait for the timercall thread to get
289            back. (sleeps or signalling)
290         b. the timercall thread is calling cancelTimer. remove any restartAfterFinished
291            request, but otherwise ignore the request to cancelTimer because it has already
292            fired. The timercall thread will return to the calling code and eventually
293            terminate in threadEventFinished.
294      3. The TimerEvent is not found. Client error or the thread returned to
295         the Timers module in between client calling cancelTimer and cancelTimer actually
296         running. Do nothing, return normally.
297
298   By making sure there is no waiting timerevent, and no running timerevent, this ensures
299   that the program cannot segfault because a timer fired on a just deleted object.
300
301   */
302
303   if (!initted) return false;
304
305   logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());
306
307   std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock
308
309   while(1)
310   {
311     TimerEvent* foundTimerEvent = NULL;
312     TimerList::iterator i;
313     for(i = timerList.begin(); i != timerList.end(); i++)
314     {
315       if (((*i)->client == client) && ((*i)->clientReference == clientReference))
316       {
317         foundTimerEvent = *i;
318         break;
319       }
320     }
321
322     if (!foundTimerEvent)
323     {
324       // Case 3, no timer found
325       return true; // unlock
326     }
327
328     // Timer found, case 1 or 2
329
330     if (!foundTimerEvent->running)
331     {
332       // Case 1. Timer was just waiting. Delete and set recalc.
333
334       timerList.erase(i);
335       delete foundTimerEvent;
336       logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
337       recalc = true;
338       timersCond.notify_all(); // shutdown could be being called? notify_all guarantees we wake masterLoop
339       return true; // unlock
340     }
341
342     if (std::this_thread::get_id() == foundTimerEvent->timerThread.get_id())
343     {
344       // Case 2 b.
345       // The thread requesting cancelTimer is the timer thread itself, the timer has already fired.
346       logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer itself calling - ignore", client, clientReference);
347       foundTimerEvent->restartAfterFinish = false; // in case a restart had already been set.
348       return true; // unlock
349     }
350
351     // Case 2 a. An external thread is calling cancelTimer for a timer which has already fired and is still running.
352     // Want to block here until we know the thread has completed.
353
354     // A broadcast notify goes out after each reap (whether in main loop or shutdown)
355     // So, wait on the cond and go around each time we wake. One of them will have been after the timerThread finished,
356     // which turns it into a case 3.
357
358     logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference);
359     timersCond.wait(lockWrapper); //unlocks in wait
360     // locked
361     logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer go-around", client, clientReference);
362
363   } // end of the big while loop
364 }
365
366
367 // Class TimerEvent
368
369 void TimerEvent::run()
370 {
371   running = true;
372   threadStartProtect.lock();
373   timerThread = std::thread([this]
374   {
375     threadStartProtect.lock();
376     threadStartProtect.unlock();
377
378     Log::getInstance()->log("Timers", Log::DEBUG, "sending timer to %p with parameter %u", client, clientReference);
379     client->timercall(clientReference);
380     Timers::getInstance()->reapTimerEvent(this);
381   });
382   threadStartProtect.unlock(); // Ensures timerThread is valid before run() returns
383 }