]> granicus.if.org Git - icinga2/blob - lib/base/timer.cpp
Update Vagrant documentation link.
[icinga2] / lib / base / timer.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "base/timer.h"
21 #include "base/application.h"
22 #include "base/debug.h"
23 #include "base/utility.h"
24 #include <boost/bind.hpp>
25 #include <boost/foreach.hpp>
26 #include <boost/thread/thread.hpp>
27 #include <boost/thread/mutex.hpp>
28 #include <boost/thread/condition_variable.hpp>
29 #include <boost/multi_index_container.hpp>
30 #include <boost/multi_index/ordered_index.hpp>
31 #include <boost/multi_index/key_extractors.hpp>
32
33 using namespace icinga;
34
35 /**
36  * @ingroup base
37  */
38 struct icinga::TimerNextExtractor
39 {
40         typedef double result_type;
41
42         /**
43          * Extracts the next timestamp from a Timer.
44          *
45          * Note: Caller must hold l_Mutex.
46          *
47          * @param wtimer Weak pointer to the timer.
48          * @returns The next timestamp
49          */
50         double operator()(const weak_ptr<Timer>& wtimer) const
51         {
52                 Timer::Ptr timer = wtimer.lock();
53
54                 if (!timer)
55                         return 0;
56
57                 return timer->m_Next;
58         }
59 };
60
61 typedef boost::multi_index_container<
62         Timer::WeakPtr,
63         boost::multi_index::indexed_by<
64                 boost::multi_index::ordered_unique<boost::multi_index::identity<Timer::WeakPtr> >,
65                 boost::multi_index::ordered_non_unique<TimerNextExtractor>
66         >
67 > TimerSet;
68
69 static boost::mutex l_Mutex;
70 static boost::condition_variable l_CV;
71 static boost::thread l_Thread;
72 static bool l_StopThread;
73 static TimerSet l_Timers;
74
75 /**
76  * Constructor for the Timer class.
77  */
78 Timer::Timer(void)
79         : m_Interval(0), m_Next(0)
80 { }
81
82 /**
83  * Initializes the timer sub-system.
84  */
85 void Timer::Initialize(void)
86 {
87         boost::mutex::scoped_lock lock(l_Mutex);
88         l_StopThread = false;
89         l_Thread = boost::thread(&Timer::TimerThreadProc);
90 }
91
92 /**
93  * Disables the timer sub-system.
94  */
95 void Timer::Uninitialize(void)
96 {
97         {
98                 boost::mutex::scoped_lock lock(l_Mutex);
99                 l_StopThread = true;
100                 l_CV.notify_all();
101         }
102
103         l_Thread.join();
104 }
105
106 /**
107  * Calls this timer.
108  */
109 void Timer::Call(void)
110 {
111         ASSERT(!OwnsLock());
112
113         Timer::Ptr self = GetSelf();
114
115         try {
116                 OnTimerExpired(self);
117         } catch (...) {
118                 Reschedule();
119
120                 throw;
121         }
122
123         Reschedule();
124 }
125
126 /**
127  * Sets the interval for this timer.
128  *
129  * @param interval The new interval.
130  */
131 void Timer::SetInterval(double interval)
132 {
133         ASSERT(!OwnsLock());
134
135         boost::mutex::scoped_lock lock(l_Mutex);
136         m_Interval = interval;
137 }
138
139 /**
140  * Retrieves the interval for this timer.
141  *
142  * @returns The interval.
143  */
144 double Timer::GetInterval(void) const
145 {
146         ASSERT(!OwnsLock());
147
148         boost::mutex::scoped_lock lock(l_Mutex);
149         return m_Interval;
150 }
151
152 /**
153  * Registers the timer and starts processing events for it.
154  */
155 void Timer::Start(void)
156 {
157         ASSERT(!OwnsLock());
158
159         {
160                 boost::mutex::scoped_lock lock(l_Mutex);
161                 m_Started = true;
162         }
163
164         Reschedule();
165 }
166
167 /**
168  * Unregisters the timer and stops processing events for it.
169  */
170 void Timer::Stop(void)
171 {
172         ASSERT(!OwnsLock());
173
174         boost::mutex::scoped_lock lock(l_Mutex);
175
176         m_Started = false;
177         l_Timers.erase(GetSelf());
178
179         /* Notify the worker thread that we've disabled a timer. */
180         l_CV.notify_all();
181 }
182
183 /**
184  * Reschedules this timer.
185  *
186  * @param next The time when this timer should be called again. Use -1 to let
187  *             the timer figure out a suitable time based on the interval.
188  */
189 void Timer::Reschedule(double next)
190 {
191         ASSERT(!OwnsLock());
192
193         boost::mutex::scoped_lock lock(l_Mutex);
194
195         if (next < 0) {
196                 /* Don't schedule the next call if this is not a periodic timer. */
197                 if (m_Interval <= 0)
198                         return;
199
200                 next = Utility::GetTime() + m_Interval;
201         }
202
203         m_Next = next;
204
205         if (m_Started) {
206                 /* Remove and re-add the timer to update the index. */
207                 l_Timers.erase(GetSelf());
208                 l_Timers.insert(GetSelf());
209
210                 /* Notify the worker that we've rescheduled a timer. */
211                 l_CV.notify_all();
212         }
213 }
214
215 /**
216  * Retrieves when the timer is next due.
217  *
218  * @returns The timestamp.
219  */
220 double Timer::GetNext(void) const
221 {
222         ASSERT(!OwnsLock());
223
224         boost::mutex::scoped_lock lock(l_Mutex);
225         return m_Next;
226 }
227
228 /**
229  * Adjusts all timers by adding the specified amount of time to their
230  * next scheduled timestamp.
231  *
232  * @param adjustment The adjustment.
233  */
234 void Timer::AdjustTimers(double adjustment)
235 {
236         boost::mutex::scoped_lock lock(l_Mutex);
237
238         double now = Utility::GetTime();
239
240         typedef boost::multi_index::nth_index<TimerSet, 1>::type TimerView;
241         TimerView& idx = boost::get<1>(l_Timers);
242
243         std::vector<Timer::Ptr> timers;
244
245         BOOST_FOREACH(const Timer::WeakPtr& wtimer, idx) {
246                 Timer::Ptr timer = wtimer.lock();
247
248                 if (!timer)
249                         continue;
250
251                 if (abs(now - (timer->m_Next + adjustment)) <
252                     abs(now - timer->m_Next)) {
253                         timer->m_Next += adjustment;
254                         timers.push_back(timer);
255                 }
256         }
257
258         BOOST_FOREACH(const Timer::Ptr& timer, timers) {
259                 l_Timers.erase(timer);
260                 l_Timers.insert(timer);
261         }
262
263         /* Notify the worker that we've rescheduled some timers. */
264         l_CV.notify_all();
265 }
266
267 /**
268  * Worker thread proc for Timer objects.
269  */
270 void Timer::TimerThreadProc(void)
271 {
272         Utility::SetThreadName("Timer Thread");
273
274         for (;;) {
275                 boost::mutex::scoped_lock lock(l_Mutex);
276
277                 typedef boost::multi_index::nth_index<TimerSet, 1>::type NextTimerView;
278                 NextTimerView& idx = boost::get<1>(l_Timers);
279
280                 /* Wait until there is at least one timer. */
281                 while (idx.empty() && !l_StopThread)
282                         l_CV.wait(lock);
283
284                 if (l_StopThread)
285                         break;
286
287                 NextTimerView::iterator it = idx.begin();
288                 Timer::Ptr timer = it->lock();
289
290                 if (!timer) {
291                         /* Remove the timer from the list if it's not alive anymore. */
292                         idx.erase(it);
293                         continue;
294                 }
295
296                 double wait = timer->m_Next - Utility::GetTime();
297
298                 if (wait > 0.01) {
299                         /* Make sure the timer we just examined can be destroyed while we're waiting. */
300                         timer.reset();
301
302                         /* Wait for the next timer. */
303                         l_CV.timed_wait(lock, boost::posix_time::milliseconds(wait * 1000));
304
305                         continue;
306                 }
307
308                 /* Remove the timer from the list so it doesn't get called again
309                  * until the current call is completed. */
310                 l_Timers.erase(timer);
311
312                 lock.unlock();
313
314                 /* Asynchronously call the timer. */
315                 Utility::QueueAsyncCallback(boost::bind(&Timer::Call, timer));
316         }
317 }