]> granicus.if.org Git - icinga2/blob - lib/base/timer.cpp
Merge pull request #7185 from Icinga/bugfix/gelfwriter-wrong-log-facility
[icinga2] / lib / base / timer.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
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>
12 #include <thread>
13
14 using namespace icinga;
15
16 namespace icinga {
17
18 class TimerHolder {
19 public:
20         TimerHolder(Timer *timer)
21                 : m_Timer(timer)
22         { }
23
24         inline Timer *GetObject() const
25         {
26                 return m_Timer;
27         }
28
29         inline double GetNextUnlocked() const
30         {
31                 return m_Timer->m_Next;
32         }
33
34         operator Timer *() const
35         {
36                 return m_Timer;
37         }
38
39 private:
40         Timer *m_Timer;
41 };
42
43 }
44
45 typedef boost::multi_index_container<
46         TimerHolder,
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> >
50         >
51 > TimerSet;
52
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;
59
60 /**
61  * Destructor for the Timer class.
62  */
63 Timer::~Timer()
64 {
65         Stop(true);
66 }
67
68 void Timer::Initialize()
69 {
70         boost::mutex::scoped_lock lock(l_TimerMutex);
71
72         if (l_AliveTimers > 0) {
73                 InitializeThread();
74         }
75 }
76
77 void Timer::Uninitialize()
78 {
79         boost::mutex::scoped_lock lock(l_TimerMutex);
80
81         if (l_AliveTimers > 0) {
82                 UninitializeThread();
83         }
84 }
85
86 void Timer::InitializeThread()
87 {
88         l_StopTimerThread = false;
89         l_TimerThread = std::thread(&Timer::TimerThreadProc);
90 }
91
92 void Timer::UninitializeThread()
93 {
94         {
95                 l_StopTimerThread = true;
96                 l_TimerCV.notify_all();
97         }
98
99         l_TimerMutex.unlock();
100
101         if (l_TimerThread.joinable())
102                 l_TimerThread.join();
103
104         l_TimerMutex.lock();
105 }
106
107 /**
108  * Calls this timer.
109  */
110 void Timer::Call()
111 {
112         try {
113                 OnTimerExpired(this);
114         } catch (...) {
115                 InternalReschedule(true);
116
117                 throw;
118         }
119
120         InternalReschedule(true);
121 }
122
123 /**
124  * Sets the interval for this timer.
125  *
126  * @param interval The new interval.
127  */
128 void Timer::SetInterval(double interval)
129 {
130         boost::mutex::scoped_lock lock(l_TimerMutex);
131         m_Interval = interval;
132 }
133
134 /**
135  * Retrieves the interval for this timer.
136  *
137  * @returns The interval.
138  */
139 double Timer::GetInterval() const
140 {
141         boost::mutex::scoped_lock lock(l_TimerMutex);
142         return m_Interval;
143 }
144
145 /**
146  * Registers the timer and starts processing events for it.
147  */
148 void Timer::Start()
149 {
150         {
151                 boost::mutex::scoped_lock lock(l_TimerMutex);
152                 m_Started = true;
153
154                 if (++l_AliveTimers == 1) {
155                         InitializeThread();
156                 }
157         }
158
159         InternalReschedule(false);
160 }
161
162 /**
163  * Unregisters the timer and stops processing events for it.
164  */
165 void Timer::Stop(bool wait)
166 {
167         if (l_StopTimerThread)
168                 return;
169
170         boost::mutex::scoped_lock lock(l_TimerMutex);
171
172         if (m_Started && --l_AliveTimers == 0) {
173                 UninitializeThread();
174         }
175
176         m_Started = false;
177         l_Timers.erase(this);
178
179         /* Notify the worker thread that we've disabled a timer. */
180         l_TimerCV.notify_all();
181
182         while (wait && m_Running)
183                 l_TimerCV.wait(lock);
184 }
185
186 void Timer::Reschedule(double next)
187 {
188         InternalReschedule(false, next);
189 }
190
191 /**
192  * Reschedules this timer.
193  *
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.
197  */
198 void Timer::InternalReschedule(bool completed, double next)
199 {
200         boost::mutex::scoped_lock lock(l_TimerMutex);
201
202         if (completed)
203                 m_Running = false;
204
205         if (next < 0) {
206                 /* Don't schedule the next call if this is not a periodic timer. */
207                 if (m_Interval <= 0)
208                         return;
209
210                 next = Utility::GetTime() + m_Interval;
211         }
212
213         m_Next = next;
214
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);
219
220                 /* Notify the worker that we've rescheduled a timer. */
221                 l_TimerCV.notify_all();
222         }
223 }
224
225 /**
226  * Retrieves when the timer is next due.
227  *
228  * @returns The timestamp.
229  */
230 double Timer::GetNext() const
231 {
232         boost::mutex::scoped_lock lock(l_TimerMutex);
233         return m_Next;
234 }
235
236 /**
237  * Adjusts all timers by adding the specified amount of time to their
238  * next scheduled timestamp.
239  *
240  * @param adjustment The adjustment.
241  */
242 void Timer::AdjustTimers(double adjustment)
243 {
244         boost::mutex::scoped_lock lock(l_TimerMutex);
245
246         double now = Utility::GetTime();
247
248         typedef boost::multi_index::nth_index<TimerSet, 1>::type TimerView;
249         TimerView& idx = boost::get<1>(l_Timers);
250
251         std::vector<Timer *> timers;
252
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);
258                 }
259         }
260
261         for (Timer *timer : timers) {
262                 l_Timers.erase(timer);
263                 l_Timers.insert(timer);
264         }
265
266         /* Notify the worker that we've rescheduled some timers. */
267         l_TimerCV.notify_all();
268 }
269
270 /**
271  * Worker thread proc for Timer objects.
272  */
273 void Timer::TimerThreadProc()
274 {
275         Log(LogDebug, "Timer", "TimerThreadProc started.");
276
277         Utility::SetThreadName("Timer Thread");
278
279         for (;;) {
280                 boost::mutex::scoped_lock lock(l_TimerMutex);
281
282                 typedef boost::multi_index::nth_index<TimerSet, 1>::type NextTimerView;
283                 NextTimerView& idx = boost::get<1>(l_Timers);
284
285                 /* Wait until there is at least one timer. */
286                 while (idx.empty() && !l_StopTimerThread)
287                         l_TimerCV.wait(lock);
288
289                 if (l_StopTimerThread)
290                         break;
291
292                 auto it = idx.begin();
293                 Timer *timer = *it;
294
295                 double wait = timer->m_Next - Utility::GetTime();
296
297                 if (wait > 0.01) {
298                         /* Wait for the next timer. */
299                         l_TimerCV.timed_wait(lock, boost::posix_time::milliseconds(long(wait * 1000)));
300
301                         continue;
302                 }
303
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);
307
308                 timer->m_Running = true;
309
310                 lock.unlock();
311
312                 /* Asynchronously call the timer. */
313                 Utility::QueueAsyncCallback([timer]() { timer->Call(); });
314         }
315 }