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