1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "base/timer.hpp"
4 #include "base/debug.hpp"
5 #include "base/logger.hpp"
6 #include "base/utility.hpp"
7 #include <boost/thread/mutex.hpp>
8 #include <boost/thread/condition_variable.hpp>
9 #include <boost/multi_index_container.hpp>
10 #include <boost/multi_index/ordered_index.hpp>
11 #include <boost/multi_index/key_extractors.hpp>
14 using namespace icinga;
20 TimerHolder(Timer *timer)
24 inline Timer *GetObject() const
29 inline double GetNextUnlocked() const
31 return m_Timer->m_Next;
34 operator Timer *() const
45 typedef boost::multi_index_container<
47 boost::multi_index::indexed_by<
48 boost::multi_index::ordered_unique<boost::multi_index::const_mem_fun<TimerHolder, Timer *, &TimerHolder::GetObject> >,
49 boost::multi_index::ordered_non_unique<boost::multi_index::const_mem_fun<TimerHolder, double, &TimerHolder::GetNextUnlocked> >
53 static boost::mutex l_TimerMutex;
54 static boost::condition_variable l_TimerCV;
55 static std::thread l_TimerThread;
56 static bool l_StopTimerThread;
57 static TimerSet l_Timers;
58 static int l_AliveTimers = 0;
61 * Destructor for the Timer class.
68 void Timer::Initialize()
70 boost::mutex::scoped_lock lock(l_TimerMutex);
72 if (l_AliveTimers > 0) {
77 void Timer::Uninitialize()
79 boost::mutex::scoped_lock lock(l_TimerMutex);
81 if (l_AliveTimers > 0) {
86 void Timer::InitializeThread()
88 l_StopTimerThread = false;
89 l_TimerThread = std::thread(&Timer::TimerThreadProc);
92 void Timer::UninitializeThread()
95 l_StopTimerThread = true;
96 l_TimerCV.notify_all();
99 l_TimerMutex.unlock();
101 if (l_TimerThread.joinable())
102 l_TimerThread.join();
113 OnTimerExpired(this);
115 InternalReschedule(true);
120 InternalReschedule(true);
124 * Sets the interval for this timer.
126 * @param interval The new interval.
128 void Timer::SetInterval(double interval)
130 boost::mutex::scoped_lock lock(l_TimerMutex);
131 m_Interval = interval;
135 * Retrieves the interval for this timer.
137 * @returns The interval.
139 double Timer::GetInterval() const
141 boost::mutex::scoped_lock lock(l_TimerMutex);
146 * Registers the timer and starts processing events for it.
151 boost::mutex::scoped_lock lock(l_TimerMutex);
154 if (++l_AliveTimers == 1) {
159 InternalReschedule(false);
163 * Unregisters the timer and stops processing events for it.
165 void Timer::Stop(bool wait)
167 if (l_StopTimerThread)
170 boost::mutex::scoped_lock lock(l_TimerMutex);
172 if (m_Started && --l_AliveTimers == 0) {
173 UninitializeThread();
177 l_Timers.erase(this);
179 /* Notify the worker thread that we've disabled a timer. */
180 l_TimerCV.notify_all();
182 while (wait && m_Running)
183 l_TimerCV.wait(lock);
186 void Timer::Reschedule(double next)
188 InternalReschedule(false, next);
192 * Reschedules this timer.
194 * @param completed Whether the timer has just completed its callback.
195 * @param next The time when this timer should be called again. Use -1 to let
196 * the timer figure out a suitable time based on the interval.
198 void Timer::InternalReschedule(bool completed, double next)
200 boost::mutex::scoped_lock lock(l_TimerMutex);
206 /* Don't schedule the next call if this is not a periodic timer. */
210 next = Utility::GetTime() + m_Interval;
215 if (m_Started && !m_Running) {
216 /* Remove and re-add the timer to update the index. */
217 l_Timers.erase(this);
218 l_Timers.insert(this);
220 /* Notify the worker that we've rescheduled a timer. */
221 l_TimerCV.notify_all();
226 * Retrieves when the timer is next due.
228 * @returns The timestamp.
230 double Timer::GetNext() const
232 boost::mutex::scoped_lock lock(l_TimerMutex);
237 * Adjusts all timers by adding the specified amount of time to their
238 * next scheduled timestamp.
240 * @param adjustment The adjustment.
242 void Timer::AdjustTimers(double adjustment)
244 boost::mutex::scoped_lock lock(l_TimerMutex);
246 double now = Utility::GetTime();
248 typedef boost::multi_index::nth_index<TimerSet, 1>::type TimerView;
249 TimerView& idx = boost::get<1>(l_Timers);
251 std::vector<Timer *> timers;
253 for (Timer *timer : idx) {
254 if (std::fabs(now - (timer->m_Next + adjustment)) <
255 std::fabs(now - timer->m_Next)) {
256 timer->m_Next += adjustment;
257 timers.push_back(timer);
261 for (Timer *timer : timers) {
262 l_Timers.erase(timer);
263 l_Timers.insert(timer);
266 /* Notify the worker that we've rescheduled some timers. */
267 l_TimerCV.notify_all();
271 * Worker thread proc for Timer objects.
273 void Timer::TimerThreadProc()
275 Log(LogDebug, "Timer", "TimerThreadProc started.");
277 Utility::SetThreadName("Timer Thread");
280 boost::mutex::scoped_lock lock(l_TimerMutex);
282 typedef boost::multi_index::nth_index<TimerSet, 1>::type NextTimerView;
283 NextTimerView& idx = boost::get<1>(l_Timers);
285 /* Wait until there is at least one timer. */
286 while (idx.empty() && !l_StopTimerThread)
287 l_TimerCV.wait(lock);
289 if (l_StopTimerThread)
292 auto it = idx.begin();
295 double wait = timer->m_Next - Utility::GetTime();
298 /* Wait for the next timer. */
299 l_TimerCV.timed_wait(lock, boost::posix_time::milliseconds(long(wait * 1000)));
304 /* Remove the timer from the list so it doesn't get called again
305 * until the current call is completed. */
306 l_Timers.erase(timer);
308 timer->m_Running = true;
312 /* Asynchronously call the timer. */
313 Utility::QueueAsyncCallback([timer]() { timer->Call(); });