summaryrefslogtreecommitdiffstats
path: root/lib/base/timer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/base/timer.cpp')
-rw-r--r--lib/base/timer.cpp354
1 files changed, 354 insertions, 0 deletions
diff --git a/lib/base/timer.cpp b/lib/base/timer.cpp
new file mode 100644
index 0000000..ffe1c39
--- /dev/null
+++ b/lib/base/timer.cpp
@@ -0,0 +1,354 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/defer.hpp"
+#include "base/timer.hpp"
+#include "base/debug.hpp"
+#include "base/logger.hpp"
+#include "base/utility.hpp"
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <utility>
+
+using namespace icinga;
+
+namespace icinga {
+
+class TimerHolder {
+public:
+ TimerHolder(Timer *timer)
+ : m_Timer(timer)
+ { }
+
+ inline Timer *GetObject() const
+ {
+ return m_Timer;
+ }
+
+ inline double GetNextUnlocked() const
+ {
+ return m_Timer->m_Next;
+ }
+
+ operator Timer *() const
+ {
+ return m_Timer;
+ }
+
+private:
+ Timer *m_Timer;
+};
+
+}
+
+typedef boost::multi_index_container<
+ TimerHolder,
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_unique<boost::multi_index::const_mem_fun<TimerHolder, Timer *, &TimerHolder::GetObject> >,
+ boost::multi_index::ordered_non_unique<boost::multi_index::const_mem_fun<TimerHolder, double, &TimerHolder::GetNextUnlocked> >
+ >
+> TimerSet;
+
+static std::mutex l_TimerMutex;
+static std::condition_variable l_TimerCV;
+static std::thread l_TimerThread;
+static bool l_StopTimerThread;
+static TimerSet l_Timers;
+static int l_AliveTimers = 0;
+
+static Defer l_ShutdownTimersCleanlyOnExit (&Timer::Uninitialize);
+
+Timer::Ptr Timer::Create()
+{
+ Ptr t (new Timer());
+
+ t->m_Self = t;
+
+ return t;
+}
+
+/**
+ * Destructor for the Timer class.
+ */
+Timer::~Timer()
+{
+ Stop(true);
+}
+
+void Timer::Initialize()
+{
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+
+ if (l_AliveTimers > 0) {
+ InitializeThread();
+ }
+}
+
+void Timer::Uninitialize()
+{
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+
+ if (l_AliveTimers > 0) {
+ UninitializeThread();
+ }
+}
+
+void Timer::InitializeThread()
+{
+ l_StopTimerThread = false;
+ l_TimerThread = std::thread(&Timer::TimerThreadProc);
+}
+
+void Timer::UninitializeThread()
+{
+ {
+ l_StopTimerThread = true;
+ l_TimerCV.notify_all();
+ }
+
+ l_TimerMutex.unlock();
+
+ if (l_TimerThread.joinable())
+ l_TimerThread.join();
+
+ l_TimerMutex.lock();
+}
+
+/**
+ * Calls this timer.
+ */
+void Timer::Call()
+{
+ try {
+ OnTimerExpired(this);
+ } catch (...) {
+ InternalReschedule(true);
+
+ throw;
+ }
+
+ InternalReschedule(true);
+}
+
+/**
+ * Sets the interval for this timer.
+ *
+ * @param interval The new interval.
+ */
+void Timer::SetInterval(double interval)
+{
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+ m_Interval = interval;
+}
+
+/**
+ * Retrieves the interval for this timer.
+ *
+ * @returns The interval.
+ */
+double Timer::GetInterval() const
+{
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+ return m_Interval;
+}
+
+/**
+ * Registers the timer and starts processing events for it.
+ */
+void Timer::Start()
+{
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+
+ if (!m_Started && ++l_AliveTimers == 1) {
+ InitializeThread();
+ }
+
+ m_Started = true;
+
+ InternalRescheduleUnlocked(false, m_Interval > 0 ? -1 : m_Next);
+}
+
+/**
+ * Unregisters the timer and stops processing events for it.
+ */
+void Timer::Stop(bool wait)
+{
+ if (l_StopTimerThread)
+ return;
+
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+
+ if (m_Started && --l_AliveTimers == 0) {
+ UninitializeThread();
+ }
+
+ m_Started = false;
+ l_Timers.erase(this);
+
+ /* Notify the worker thread that we've disabled a timer. */
+ l_TimerCV.notify_all();
+
+ while (wait && m_Running)
+ l_TimerCV.wait(lock);
+}
+
+void Timer::Reschedule(double next)
+{
+ InternalReschedule(false, next);
+}
+
+void Timer::InternalReschedule(bool completed, double next)
+{
+ std::unique_lock<std::mutex> lock (l_TimerMutex);
+
+ InternalRescheduleUnlocked(completed, next);
+}
+
+/**
+ * Reschedules this timer.
+ *
+ * @param completed Whether the timer has just completed its callback.
+ * @param next The time when this timer should be called again. Use -1 to let
+ * the timer figure out a suitable time based on the interval.
+ */
+void Timer::InternalRescheduleUnlocked(bool completed, double next)
+{
+ if (completed)
+ m_Running = false;
+
+ if (next < 0) {
+ /* Don't schedule the next call if this is not a periodic timer. */
+ if (m_Interval <= 0)
+ return;
+
+ next = Utility::GetTime() + m_Interval;
+ }
+
+ m_Next = next;
+
+ if (m_Started && !m_Running) {
+ /* Remove and re-add the timer to update the index. */
+ l_Timers.erase(this);
+ l_Timers.insert(this);
+
+ /* Notify the worker that we've rescheduled a timer. */
+ l_TimerCV.notify_all();
+ }
+}
+
+/**
+ * Retrieves when the timer is next due.
+ *
+ * @returns The timestamp.
+ */
+double Timer::GetNext() const
+{
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+ return m_Next;
+}
+
+/**
+ * Adjusts all periodic timers by adding the specified amount of time to their
+ * next scheduled timestamp.
+ *
+ * @param adjustment The adjustment.
+ */
+void Timer::AdjustTimers(double adjustment)
+{
+ std::unique_lock<std::mutex> lock(l_TimerMutex);
+
+ double now = Utility::GetTime();
+
+ typedef boost::multi_index::nth_index<TimerSet, 1>::type TimerView;
+ TimerView& idx = boost::get<1>(l_Timers);
+
+ std::vector<Timer *> timers;
+
+ for (Timer *timer : idx) {
+ /* Don't schedule the next call if this is not a periodic timer. */
+ if (timer->m_Interval <= 0) {
+ continue;
+ }
+
+ if (std::fabs(now - (timer->m_Next + adjustment)) <
+ std::fabs(now - timer->m_Next)) {
+ timer->m_Next += adjustment;
+ timers.push_back(timer);
+ }
+ }
+
+ for (Timer *timer : timers) {
+ l_Timers.erase(timer);
+ l_Timers.insert(timer);
+ }
+
+ /* Notify the worker that we've rescheduled some timers. */
+ l_TimerCV.notify_all();
+}
+
+/**
+ * Worker thread proc for Timer objects.
+ */
+void Timer::TimerThreadProc()
+{
+ namespace ch = std::chrono;
+
+ Log(LogDebug, "Timer", "TimerThreadProc started.");
+
+ Utility::SetThreadName("Timer Thread");
+
+ std::unique_lock<std::mutex> lock (l_TimerMutex);
+
+ for (;;) {
+ typedef boost::multi_index::nth_index<TimerSet, 1>::type NextTimerView;
+ NextTimerView& idx = boost::get<1>(l_Timers);
+
+ /* Wait until there is at least one timer. */
+ while (idx.empty() && !l_StopTimerThread)
+ l_TimerCV.wait(lock);
+
+ if (l_StopTimerThread)
+ break;
+
+ auto it = idx.begin();
+
+ // timer->~Timer() may be called at any moment (if the last
+ // smart pointer gets destroyed) or even already waiting for
+ // l_TimerMutex (before doing anything else) which we have
+ // locked at the moment. Until our unlock using *timer is safe.
+ Timer *timer = *it;
+
+ ch::time_point<ch::system_clock, ch::duration<double>> next (ch::duration<double>(timer->m_Next));
+
+ if (next - ch::system_clock::now() > ch::duration<double>(0.01)) {
+ /* Wait for the next timer. */
+ l_TimerCV.wait_until(lock, next);
+
+ continue;
+ }
+
+ /* Remove the timer from the list so it doesn't get called again
+ * until the current call is completed. */
+ l_Timers.erase(timer);
+
+ auto keepAlive (timer->m_Self.lock());
+
+ if (!keepAlive) {
+ // The last std::shared_ptr is gone, let ~Timer() proceed
+ continue;
+ }
+
+ timer->m_Running = true;
+
+ lock.unlock();
+
+ /* Asynchronously call the timer. */
+ Utility::QueueAsyncCallback([timer=std::move(keepAlive)]() { timer->Call(); });
+
+ lock.lock();
+ }
+}