2 Copyright 2004-2020 Chris Tallon
4 This file is part of VOMP.
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.
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.
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/>.
24 Timers* Timers::instance = NULL;
37 Timers* Timers::getInstance()
44 if (initted) return false;
45 logger = Log::getInstance();
47 timersMutex.lock(); // Start thread with mutex locked
49 timersThread = std::thread([this] { masterLoop(); });
50 logger->log("Timers", Log::DEBUG, "Timers started");
55 void Timers::shutdown()
59 std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock the mutex
60 // main loop is in cond.wait/_until
62 // stop the main thread
63 quitThread = true; // main loop effectively dead, but join it later
65 // Remove any future timer events from the list
66 for(auto i = timerList.begin(); i != timerList.end(); )
73 i = timerList.erase(i);
79 // Now wait for all timer event threads to end
80 while(timerList.size())
82 timersCond.wait(lockWrapper); // unlocks in wait. waiting for reap signals
84 // Assume there has been a reap signal. Could be spurious but no harm done
86 timersCond.notify_all(); // In case of waiting cancelTimers
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
94 bool Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs)
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);
100 return setTimerC(client, clientReference, fireTime);
103 bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSecs)
105 std::chrono::system_clock::time_point fireTime = std::chrono::system_clock::from_time_t(requestedTime);
106 fireTime += std::chrono::nanoseconds(requestedTimeNSecs);
108 return setTimerC(client, clientReference, fireTime);
111 bool Timers::setTimerC(TimerReceiver* client, int clientReference, std::chrono::system_clock::time_point& fireTime)
113 if (!initted) return 0;
115 logger->log("Timers", Log::DEBUG, "Starting set timer chrono");
117 std::lock_guard<std::mutex> lock(timersMutex);
119 // Check that this timer is not already in the list
120 for(auto foundTimerEvent : timerList)
122 if ((foundTimerEvent->client == client) && (foundTimerEvent->clientReference == clientReference))
124 // Timer exists already, either waiting or running
125 // Update requestedTime
126 foundTimerEvent->requestedTime = fireTime;
128 if (foundTimerEvent->running)
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
138 logger->log("Timers", Log::DEBUG, "Editing existing timer");
140 // A waiting timer has been edited
142 timersCond.notify_all();
143 return true; // unlock
148 // Timer did not exist already
150 TimerEvent* t = new TimerEvent();
152 t->clientReference = clientReference;
153 t->requestedTime = fireTime;
155 timerList.push_back(t);
157 timersCond.notify_all();
158 logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference);
159 return true; // unlock
162 void Timers::masterLoop()
164 // mutex is locked already
165 std::unique_lock<std::mutex> lockWrapper(timersMutex, std::defer_lock);
167 TimerEvent* nextTimer = NULL;
173 // work out the next fire time
176 for(TimerEvent* thisTimer : timerList)
178 if (thisTimer->running) continue; // has already been timercall'd
182 nextTimer = thisTimer;
186 if (thisTimer->requestedTime < nextTimer->requestedTime)
188 nextTimer = thisTimer;
199 // wait for signal timed
200 cvs = timersCond.wait_until(lockWrapper, nextTimer->requestedTime); //unlocks in wait
205 timersCond.wait(lockWrapper); //unlocks in wait
208 // and we're back - we've been signalled (quit, recalc, reap) or the time ran out, or spurious. mutex locked.
211 if (quitThread) return; // unlocks motex
216 timersCond.notify_all(); // in case of waiting cancelTimers
219 // recalc? -> restart loop
220 if (recalc) continue;
222 // time ran out? -> fire a timer
223 if (nextTimer && (cvs == std::cv_status::timeout))
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.
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
241 void Timers::reap() // Master timers thread, mutex locked (or shutdown, mutex locked)
243 for(auto i = timerList.begin(); i != timerList.end(); )
249 te->timerThread.join();
251 if (te->restartAfterFinish)
253 logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p %i", te->client, te->clientReference);
254 te->restartAfterFinish = false;
256 te->completed = false;
263 i = timerList.erase(i);
271 void Timers::reapTimerEvent(TimerEvent* te) // Called by a TimerEvent thread
273 std::lock_guard<std::mutex> lock(timersMutex);
274 te->completed = 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
281 bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
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.
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.
305 if (!initted) return false;
307 logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());
309 std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock
313 TimerEvent* foundTimerEvent = NULL;
314 TimerList::iterator i;
315 for(i = timerList.begin(); i != timerList.end(); i++)
317 if (((*i)->client == client) && ((*i)->clientReference == clientReference))
319 foundTimerEvent = *i;
324 if (!foundTimerEvent)
326 // Case 3, no timer found
327 return true; // unlock
330 // Timer found, case 1 or 2
332 if (!foundTimerEvent->running)
334 // Case 1. Timer was just waiting. Delete and set recalc.
337 delete foundTimerEvent;
338 logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
340 timersCond.notify_all(); // shutdown could be being called? notify_all guarantees we wake masterLoop
341 return true; // unlock
344 if (std::this_thread::get_id() == foundTimerEvent->timerThread.get_id())
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
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.
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.
360 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference);
361 timersCond.wait(lockWrapper); //unlocks in wait
363 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer go-around", client, clientReference);
365 } // end of the big while loop
371 void TimerEvent::run()
374 threadStartProtect.lock();
375 timerThread = std::thread([this]
377 threadStartProtect.lock();
378 threadStartProtect.unlock();
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);
384 threadStartProtect.unlock(); // Ensures timerThread is valid before run() returns