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 fireTime += std::chrono::duration_cast<std::chrono::seconds>(std::chrono::nanoseconds(requestedNSecs));
101 return setTimerC(client, clientReference, fireTime);
104 bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSecs)
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));
110 return setTimerC(client, clientReference, fireTime);
113 bool Timers::setTimerC(TimerReceiver* client, int clientReference, std::chrono::system_clock::time_point& fireTime)
115 if (!initted) return 0;
117 logger->log("Timers", Log::DEBUG, "Starting set timer chrono");
119 std::lock_guard<std::mutex> lock(timersMutex);
121 // Check that this timer is not already in the list
122 for(auto foundTimerEvent : timerList)
124 if ((foundTimerEvent->client == client) && (foundTimerEvent->clientReference == clientReference))
126 // Timer exists already, either waiting or running
127 // Update requestedTime
128 foundTimerEvent->requestedTime = fireTime;
130 if (foundTimerEvent->running)
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
140 logger->log("Timers", Log::DEBUG, "Editing existing timer");
142 // A waiting timer has been edited
144 timersCond.notify_all();
145 return true; // unlock
150 // Timer did not exist already
152 TimerEvent* t = new TimerEvent();
154 t->clientReference = clientReference;
155 t->requestedTime = fireTime;
157 timerList.push_back(t);
159 timersCond.notify_all();
160 logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference);
161 return true; // unlock
164 void Timers::masterLoop()
166 // mutex is locked already
167 std::unique_lock<std::mutex> lockWrapper(timersMutex, std::defer_lock);
169 TimerEvent* nextTimer = NULL;
175 // work out the next fire time
178 for(TimerEvent* thisTimer : timerList)
180 if (thisTimer->running) continue; // has already been timercall'd
184 nextTimer = thisTimer;
188 if (thisTimer->requestedTime < nextTimer->requestedTime)
190 nextTimer = thisTimer;
201 // wait for signal timed
202 cvs = timersCond.wait_until(lockWrapper, nextTimer->requestedTime); //unlocks in wait
207 timersCond.wait(lockWrapper); //unlocks in wait
210 // and we're back - we've been signalled (quit, recalc, reap) or the time ran out, or spurious. mutex locked.
213 if (quitThread) return; // unlocks motex
218 timersCond.notify_all(); // in case of waiting cancelTimers
221 // recalc? -> restart loop
222 if (recalc) continue;
224 // time ran out? -> fire a timer
225 if (nextTimer && (cvs == std::cv_status::timeout))
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.
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
243 void Timers::reap() // Master timers thread, mutex locked (or shutdown, mutex locked)
245 for(auto i = timerList.begin(); i != timerList.end(); )
251 te->timerThread.join();
253 if (te->restartAfterFinish)
255 logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p %i", te->client, te->clientReference);
256 te->restartAfterFinish = false;
258 te->completed = false;
265 i = timerList.erase(i);
273 void Timers::reapTimerEvent(TimerEvent* te) // Called by a TimerEvent thread
275 std::lock_guard<std::mutex> lock(timersMutex);
276 te->completed = 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
283 bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
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.
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.
307 if (!initted) return false;
309 logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());
311 std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock
315 TimerEvent* foundTimerEvent = NULL;
316 TimerList::iterator i;
317 for(i = timerList.begin(); i != timerList.end(); i++)
319 if (((*i)->client == client) && ((*i)->clientReference == clientReference))
321 foundTimerEvent = *i;
326 if (!foundTimerEvent)
328 // Case 3, no timer found
329 return true; // unlock
332 // Timer found, case 1 or 2
334 if (!foundTimerEvent->running)
336 // Case 1. Timer was just waiting. Delete and set recalc.
339 delete foundTimerEvent;
340 logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
342 timersCond.notify_all(); // shutdown could be being called? notify_all guarantees we wake masterLoop
343 return true; // unlock
346 if (std::this_thread::get_id() == foundTimerEvent->timerThread.get_id())
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
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.
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.
362 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference);
363 timersCond.wait(lockWrapper); //unlocks in wait
365 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer go-around", client, clientReference);
367 } // end of the big while loop
373 void TimerEvent::run()
376 threadStartProtect.lock();
377 timerThread = std::thread([this]
379 threadStartProtect.lock();
380 threadStartProtect.unlock();
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);
386 threadStartProtect.unlock(); // Ensures timerThread is valid before run() returns