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 static const char* TAG = "Timers";
26 Timers* Timers::instance = NULL;
39 Timers* Timers::getInstance()
46 if (initted) return false;
47 logger = LogNT::getInstance();
49 timersMutex.lock(); // Start thread with mutex locked
51 timersThread = std::thread([this] { masterLoop(); });
52 logger->debug(TAG, "Timers started");
57 void Timers::shutdown()
61 std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock the mutex
62 // main loop is in cond.wait/_until
64 // stop the main thread
65 quitThread = true; // main loop effectively dead, but join it later
67 // Remove any future timer events from the list
68 for(auto i = timerList.begin(); i != timerList.end(); )
75 i = timerList.erase(i);
81 // Now wait for all timer event threads to end
82 while(timerList.size())
84 timersCond.wait(lockWrapper); // unlocks in wait. waiting for reap signals
86 // Assume there has been a reap signal. Could be spurious but no harm done
88 timersCond.notify_all(); // In case of waiting cancelTimers
91 timersCond.notify_one(); // In case there were no TimerEvents to reap, wake the masterLoop thread
92 lockWrapper.unlock(); // main loop exits if it hasn't already
96 bool Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs)
98 std::chrono::system_clock::time_point fireTime = std::chrono::system_clock::now();
99 fireTime += std::chrono::seconds(requestedSecs);
101 fireTime += std::chrono::microseconds(requestedNSecs / 1000);
103 fireTime += std::chrono::nanoseconds(requestedNSecs);
105 return setTimerC(client, clientReference, fireTime);
108 bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSecs)
110 std::chrono::system_clock::time_point fireTime = std::chrono::system_clock::from_time_t(requestedTime);
112 fireTime += std::chrono::microseconds(requestedTimeNSecs / 1000);
114 fireTime += std::chrono::nanoseconds(requestedTimeNSecs);
116 return setTimerC(client, clientReference, fireTime);
119 bool Timers::setTimerC(TimerReceiver* client, int clientReference, std::chrono::system_clock::time_point& fireTime)
121 if (!initted) return 0;
123 logger->debug(TAG, "Starting set timer chrono");
125 std::lock_guard<std::mutex> lock(timersMutex);
127 // Check that this timer is not already in the list
128 for(auto foundTimerEvent : timerList)
130 if ((foundTimerEvent->client == client) && (foundTimerEvent->clientReference == clientReference))
132 // Timer exists already, either waiting or running
133 // Update requestedTime
134 foundTimerEvent->requestedTime = fireTime;
136 if (foundTimerEvent->running)
138 // If this timerEvent is currently running, update the clocks and set the restart flag.
139 // Instead of being deleted in timerEventFinished it will be restarted
140 foundTimerEvent->restartAfterFinish = true;
141 // Don't need to resetThreadFlag because this timer isn't re-live yet
142 return true; // unlock
146 logger->debug(TAG, "Editing existing timer");
148 // A waiting timer has been edited
150 timersCond.notify_all();
151 return true; // unlock
156 // Timer did not exist already
158 TimerEvent* t = new TimerEvent();
160 t->clientReference = clientReference;
161 t->requestedTime = fireTime;
163 timerList.push_back(t);
165 timersCond.notify_all();
166 logger->debug(TAG, "Timer set for {} ref {}", static_cast<void*>(client), clientReference);
167 return true; // unlock
170 void Timers::masterLoop()
172 // mutex is locked already
173 std::unique_lock<std::mutex> lockWrapper(timersMutex, std::defer_lock);
175 TimerEvent* nextTimer = NULL;
181 // work out the next fire time
184 for(TimerEvent* thisTimer : timerList)
186 if (thisTimer->running) continue; // has already been timercall'd
190 nextTimer = thisTimer;
194 if (thisTimer->requestedTime < nextTimer->requestedTime)
196 nextTimer = thisTimer;
207 // wait for signal timed
208 cvs = timersCond.wait_until(lockWrapper, nextTimer->requestedTime); //unlocks in wait
213 timersCond.wait(lockWrapper); //unlocks in wait
216 // and we're back - we've been signalled (quit, recalc, reap) or the time ran out, or spurious. mutex locked.
219 if (quitThread) return; // unlocks motex
224 timersCond.notify_all(); // in case of waiting cancelTimers
227 // recalc? -> restart loop
228 if (recalc) continue;
230 // time ran out? -> fire a timer
231 if (nextTimer && (cvs == std::cv_status::timeout))
238 // not quit, not recalc, and either:
239 // 1. there is no next timer
240 // 2. there is a next timer but the timeout didn't expire
241 // therefore, this is a spurious wakeup. Leave recalc == false, go around, and do wait/wait_until again.
246 // 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
249 void Timers::reap() // Master timers thread, mutex locked (or shutdown, mutex locked)
251 for(auto i = timerList.begin(); i != timerList.end(); )
257 te->timerThread.join();
259 if (te->restartAfterFinish)
261 logger->debug(TAG, "timerEventFinished RESTART for {} {}", static_cast<void*>(te->client), te->clientReference);
262 te->restartAfterFinish = false;
264 te->completed = false;
271 i = timerList.erase(i);
279 void Timers::reapTimerEvent(TimerEvent* te) // Called by a TimerEvent thread
281 std::lock_guard<std::mutex> lock(timersMutex);
282 te->completed = true;
284 timersCond.notify_all(); // Would be notify_one, but in the case of shutdown the thread within shutdown() will be
285 // handling the reaping. The notify that goes to the masterLoop will cause it to return from wait and sit waiting
286 // for the lock so it can examine quitThread and return. Doesn't matter which thread is unblocked first
289 bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
291 /* This method locks the timers mutex
292 Then one of three things can happen:
293 1. The TimerEvent is found, running = false. This means it hasn't started yet.
294 Delete the timer normally, set recalc
295 2. The TimerEvent is found, running = true. This means the timer is currently firing,
296 timercall on the client is being called.
297 a. Thread calling cancelTimer is an external thread: In this case, this thread
298 calling cancelTimer needs to unlock and wait for the timercall thread to get
299 back. (sleeps or signalling)
300 b. the timercall thread is calling cancelTimer. remove any restartAfterFinished
301 request, but otherwise ignore the request to cancelTimer because it has already
302 fired. The timercall thread will return to the calling code and eventually
303 terminate in threadEventFinished.
304 3. The TimerEvent is not found. Client error or the thread returned to
305 the Timers module in between client calling cancelTimer and cancelTimer actually
306 running. Do nothing, return normally.
308 By making sure there is no waiting timerevent, and no running timerevent, this ensures
309 that the program cannot segfault because a timer fired on a just deleted object.
313 if (!initted) return false;
315 logger->debug(TAG, "Starting cancel timer {} {}, list size = {}", static_cast<void*>(client), clientReference, timerList.size());
317 std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock
321 TimerEvent* foundTimerEvent = NULL;
322 TimerList::iterator i;
323 for(i = timerList.begin(); i != timerList.end(); i++)
325 if (((*i)->client == client) && ((*i)->clientReference == clientReference))
327 foundTimerEvent = *i;
332 if (!foundTimerEvent)
334 // Case 3, no timer found
335 return true; // unlock
338 // Timer found, case 1 or 2
340 if (!foundTimerEvent->running)
342 // Case 1. Timer was just waiting. Delete and set recalc.
345 delete foundTimerEvent;
346 logger->debug(TAG, "Removed timer for {} ref {}", static_cast<void*>(client), clientReference);
348 timersCond.notify_all(); // shutdown could be being called? notify_all guarantees we wake masterLoop
349 return true; // unlock
352 if (std::this_thread::get_id() == foundTimerEvent->timerThread.get_id())
355 // The thread requesting cancelTimer is the timer thread itself, the timer has already fired.
356 logger->debug(TAG, "{} ref {} cancelTimer itself calling - ignore", static_cast<void*>(client), clientReference);
357 foundTimerEvent->restartAfterFinish = false; // in case a restart had already been set.
358 return true; // unlock
361 // Case 2 a. An external thread is calling cancelTimer for a timer which has already fired and is still running.
362 // Want to block here until we know the thread has completed.
364 // A broadcast notify goes out after each reap (whether in main loop or shutdown)
365 // So, wait on the cond and go around each time we wake. One of them will have been after the timerThread finished,
366 // which turns it into a case 3.
368 logger->debug(TAG, "{} ref {} cancelTimer WAITING", static_cast<void*>(client), clientReference);
369 timersCond.wait(lockWrapper); //unlocks in wait
371 logger->debug(TAG, "{} ref {} cancelTimer go-around", static_cast<void*>(client), clientReference);
373 } // end of the big while loop
379 void TimerEvent::run()
382 threadStartProtect.lock();
383 timerThread = std::thread([this]
385 threadStartProtect.lock();
386 threadStartProtect.unlock();
388 LogNT::getInstance()->debug(TAG, "sending timer to {} with parameter {}", static_cast<void*>(client), clientReference);
389 client->timercall(clientReference);
390 Timers::getInstance()->reapTimerEvent(this);
392 threadStartProtect.unlock(); // Ensures timerThread is valid before run() returns