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