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