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