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);
99 fireTime += std::chrono::microseconds(requestedNSecs / 1000);
101 fireTime += std::chrono::nanoseconds(requestedNSecs);
103 return setTimerC(client, clientReference, fireTime);
106 bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSecs)
108 std::chrono::system_clock::time_point fireTime = std::chrono::system_clock::from_time_t(requestedTime);
110 fireTime += std::chrono::microseconds(requestedTimeNSecs / 1000);
112 fireTime += std::chrono::nanoseconds(requestedTimeNSecs);
114 return setTimerC(client, clientReference, fireTime);
117 bool Timers::setTimerC(TimerReceiver* client, int clientReference, std::chrono::system_clock::time_point& fireTime)
119 if (!initted) return 0;
121 logger->log("Timers", Log::DEBUG, "Starting set timer chrono");
123 std::lock_guard<std::mutex> lock(timersMutex);
125 // Check that this timer is not already in the list
126 for(auto foundTimerEvent : timerList)
128 if ((foundTimerEvent->client == client) && (foundTimerEvent->clientReference == clientReference))
130 // Timer exists already, either waiting or running
131 // Update requestedTime
132 foundTimerEvent->requestedTime = fireTime;
134 if (foundTimerEvent->running)
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
144 logger->log("Timers", Log::DEBUG, "Editing existing timer");
146 // A waiting timer has been edited
148 timersCond.notify_all();
149 return true; // unlock
154 // Timer did not exist already
156 TimerEvent* t = new TimerEvent();
158 t->clientReference = clientReference;
159 t->requestedTime = fireTime;
161 timerList.push_back(t);
163 timersCond.notify_all();
164 logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference);
165 return true; // unlock
168 void Timers::masterLoop()
170 // mutex is locked already
171 std::unique_lock<std::mutex> lockWrapper(timersMutex, std::defer_lock);
173 TimerEvent* nextTimer = NULL;
179 // work out the next fire time
182 for(TimerEvent* thisTimer : timerList)
184 if (thisTimer->running) continue; // has already been timercall'd
188 nextTimer = thisTimer;
192 if (thisTimer->requestedTime < nextTimer->requestedTime)
194 nextTimer = thisTimer;
205 // wait for signal timed
206 cvs = timersCond.wait_until(lockWrapper, nextTimer->requestedTime); //unlocks in wait
211 timersCond.wait(lockWrapper); //unlocks in wait
214 // and we're back - we've been signalled (quit, recalc, reap) or the time ran out, or spurious. mutex locked.
217 if (quitThread) return; // unlocks motex
222 timersCond.notify_all(); // in case of waiting cancelTimers
225 // recalc? -> restart loop
226 if (recalc) continue;
228 // time ran out? -> fire a timer
229 if (nextTimer && (cvs == std::cv_status::timeout))
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.
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
247 void Timers::reap() // Master timers thread, mutex locked (or shutdown, mutex locked)
249 for(auto i = timerList.begin(); i != timerList.end(); )
255 te->timerThread.join();
257 if (te->restartAfterFinish)
259 logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p %i", te->client, te->clientReference);
260 te->restartAfterFinish = false;
262 te->completed = false;
269 i = timerList.erase(i);
277 void Timers::reapTimerEvent(TimerEvent* te) // Called by a TimerEvent thread
279 std::lock_guard<std::mutex> lock(timersMutex);
280 te->completed = 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
287 bool Timers::cancelTimer(TimerReceiver* client, int clientReference)
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.
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.
311 if (!initted) return false;
313 logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size());
315 std::unique_lock<std::mutex> lockWrapper(timersMutex); // lock
319 TimerEvent* foundTimerEvent = NULL;
320 TimerList::iterator i;
321 for(i = timerList.begin(); i != timerList.end(); i++)
323 if (((*i)->client == client) && ((*i)->clientReference == clientReference))
325 foundTimerEvent = *i;
330 if (!foundTimerEvent)
332 // Case 3, no timer found
333 return true; // unlock
336 // Timer found, case 1 or 2
338 if (!foundTimerEvent->running)
340 // Case 1. Timer was just waiting. Delete and set recalc.
343 delete foundTimerEvent;
344 logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference);
346 timersCond.notify_all(); // shutdown could be being called? notify_all guarantees we wake masterLoop
347 return true; // unlock
350 if (std::this_thread::get_id() == foundTimerEvent->timerThread.get_id())
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
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.
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.
366 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference);
367 timersCond.wait(lockWrapper); //unlocks in wait
369 logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer go-around", client, clientReference);
371 } // end of the big while loop
377 void TimerEvent::run()
380 threadStartProtect.lock();
381 timerThread = std::thread([this]
383 threadStartProtect.lock();
384 threadStartProtect.unlock();
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);
390 threadStartProtect.unlock(); // Ensures timerThread is valid before run() returns