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);
99 return setTimerC(client, clientReference, fireTime);
102 bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSecs)
104 std::chrono::system_clock::time_point fireTime = std::chrono::system_clock::from_time_t(requestedTime);
105 fireTime += std::chrono::nanoseconds(requestedTimeNSecs);
106 return setTimerC(client, clientReference, fireTime);
109 bool Timers::setTimerC(TimerReceiver* client, int clientReference, std::chrono::system_clock::time_point& fireTime)
111 if (!initted) return 0;
113 logger->log("Timers", Log::DEBUG, "Starting set timer chrono");
115 std::lock_guard<std::mutex> lock(timersMutex);
117 // Check that this timer is not already in the list
118 for(auto foundTimerEvent : timerList)
120 if ((foundTimerEvent->client == client) && (foundTimerEvent->clientReference == clientReference))
122 // Timer exists already, either waiting or running
123 // Update requestedTime
124 foundTimerEvent->requestedTime = fireTime;
126 if (foundTimerEvent->running)
128 // If this timerEvent is currently running, update the clocks and set the restart flag.
129 // Instead of being deleted in timerEventFinished it will be restarted
130 foundTimerEvent->restartAfterFinish = true;
131 // Don't need to resetThreadFlag because this timer isn't re-live yet
132 return true; // unlock
136 logger->log("Timers", Log::DEBUG, "Editing existing timer");
138 // A waiting timer has been edited
140 timersCond.notify_all();
141 return true; // unlock
146 // Timer did not exist already
148 TimerEvent* t = new TimerEvent();
150 t->clientReference = clientReference;
151 t->requestedTime = fireTime;
153 timerList.push_back(t);
155 timersCond.notify_all();
156 logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference);
157 return true; // unlock
160 void Timers::masterLoop()
162 // mutex is locked already
163 std::unique_lock<std::mutex> lockWrapper(timersMutex, std::defer_lock);
165 TimerEvent* nextTimer = NULL;
171 // work out the next fire time
174 for(TimerEvent* thisTimer : timerList)
176 if (thisTimer->running) continue; // has already been timercall'd
180 nextTimer = thisTimer;
184 if (thisTimer->requestedTime < nextTimer->requestedTime)
186 nextTimer = thisTimer;
197 // wait for signal timed
198 cvs = timersCond.wait_until(lockWrapper, nextTimer->requestedTime); //unlocks in wait
203 timersCond.wait(lockWrapper); //unlocks in wait
206 // and we're back - we've been signalled (quit, recalc, reap) or the time ran out, or spurious. mutex locked.
209 if (quitThread) return; // unlocks motex
214 timersCond.notify_all(); // in case of waiting cancelTimers
217 // recalc? -> restart loop
218 if (recalc) continue;
220 // time ran out? -> fire a timer
221 if (nextTimer && (cvs == std::cv_status::timeout))
228 // not quit, not recalc, and either:
229 // 1. there is no next timer
230 // 2. there is a next timer but the timeout didn't expire
231 // therefore, this is a spurious wakeup. Leave recalc == false, go around, and do wait/wait_until again.
236 // 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 void Timers::reap() // Master timers thread, mutex locked (or shutdown, mutex locked)
241 for(auto i = timerList.begin(); i != timerList.end(); )
247 te->timerThread.join();
249 if (te->restartAfterFinish)
251 logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p %i", te->client, te->clientReference);
252 te->restartAfterFinish = false;
254 te->completed = false;
261 i = timerList.erase(i);
269 void Timers::reapTimerEvent(TimerEvent* te) // Called by a TimerEvent thread
271 std::lock_guard<std::mutex> lock(timersMutex);
272 te->completed = true;
274 timersCond.notify_all(); // Would be notify_one, but in the case of shutdown the thread within shutdown() will be
275 // handling the reaping. The notify that goes to the masterLoop will cause it to return from wait and sit waiting
276 // for the lock so it can examine quitThread and return. Doesn't matter which thread is unblocked first
279 bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
281 /* This method locks the timers mutex
282 Then one of three things can happen:
283 1. The TimerEvent is found, running = false. This means it hasn't started yet.
284 Delete the timer normally, set recalc
285 2. The TimerEvent is found, running = true. This means the timer is currently firing,
286 timercall on the client is being called.
287 a. Thread calling cancelTimer is an external thread: In this case, this thread
288 calling cancelTimer needs to unlock and wait for the timercall thread to get
289 back. (sleeps or signalling)
290 b. the timercall thread is calling cancelTimer. remove any restartAfterFinished
291 request, but otherwise ignore the request to cancelTimer because it has already
292 fired. The timercall thread will return to the calling code and eventually
293 terminate in threadEventFinished.
294 3. The TimerEvent is not found. Client error or the thread returned to
295 the Timers module in between client calling cancelTimer and cancelTimer actually
296 running. Do nothing, return normally.
298 By making sure there is no waiting timerevent, and no running timerevent, this ensures
299 that the program cannot segfault because a timer fired on a just deleted object.
303 if (!initted) return false;
305 logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());
307 std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock
311 TimerEvent* foundTimerEvent = NULL;
312 TimerList::iterator i;
313 for(i = timerList.begin(); i != timerList.end(); i++)
315 if (((*i)->client == client) && ((*i)->clientReference == clientReference))
317 foundTimerEvent = *i;
322 if (!foundTimerEvent)
324 // Case 3, no timer found
325 return true; // unlock
328 // Timer found, case 1 or 2
330 if (!foundTimerEvent->running)
332 // Case 1. Timer was just waiting. Delete and set recalc.
335 delete foundTimerEvent;
336 logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
338 timersCond.notify_all(); // shutdown could be being called? notify_all guarantees we wake masterLoop
339 return true; // unlock
342 if (std::this_thread::get_id() == foundTimerEvent->timerThread.get_id())
345 // The thread requesting cancelTimer is the timer thread itself, the timer has already fired.
346 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer itself calling - ignore", client, clientReference);
347 foundTimerEvent->restartAfterFinish = false; // in case a restart had already been set.
348 return true; // unlock
351 // Case 2 a. An external thread is calling cancelTimer for a timer which has already fired and is still running.
352 // Want to block here until we know the thread has completed.
354 // A broadcast notify goes out after each reap (whether in main loop or shutdown)
355 // So, wait on the cond and go around each time we wake. One of them will have been after the timerThread finished,
356 // which turns it into a case 3.
358 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference);
359 timersCond.wait(lockWrapper); //unlocks in wait
361 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer go-around", client, clientReference);
363 } // end of the big while loop
369 void TimerEvent::run()
372 threadStartProtect.lock();
373 timerThread = std::thread([this]
375 threadStartProtect.lock();
376 threadStartProtect.unlock();
378 Log::getInstance()->log("Timers", Log::DEBUG, "sending timer to %p with parameter %u", client, clientReference);
379 client->timercall(clientReference);
380 Timers::getInstance()->reapTimerEvent(this);
382 threadStartProtect.unlock(); // Ensures timerThread is valid before run() returns