Add common sd_event timer class
timer.hpp and timer.cpp are used throughout the project by copying them
into whatever git repo they are needed. This puts a header-only
implementation into sdbusplus, which is also used by every project.
Then, if you want a timer, you include sdbusplus/timer.hpp and you are
good to go.
Change-Id: Ica7543ecb66128b645f609e790fa7183eeb34ac1
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index 32fe3ec..ab07920 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,6 +24,7 @@
sdbusplus/server/manager.hpp \
sdbusplus/server/object.hpp \
sdbusplus/slot.hpp \
+ sdbusplus/timer.hpp \
sdbusplus/utility/container_traits.hpp \
sdbusplus/utility/read_into_tuple.hpp \
sdbusplus/utility/tuple_to_array.hpp \
diff --git a/sdbusplus/timer.hpp b/sdbusplus/timer.hpp
new file mode 100644
index 0000000..20ac203
--- /dev/null
+++ b/sdbusplus/timer.hpp
@@ -0,0 +1,253 @@
+#pragma once
+
+#include <systemd/sd-event.h>
+
+#include <chrono>
+#include <functional>
+
+namespace phosphor
+{
+
+/** @class Timer
+ * @brief Manages starting watchdog timers and handling timeouts
+ */
+class Timer
+{
+ public:
+ /** @brief Only need the default Timer */
+ Timer() = delete;
+ Timer(const Timer&) = delete;
+ Timer& operator=(const Timer&) = delete;
+ Timer(Timer&&) = delete;
+ Timer& operator=(Timer&&) = delete;
+
+ /** @brief Constructs timer object
+ * Uses the default sd_event object
+ *
+ * @param[in] funcCallBack - optional function callback for timer
+ * expirations
+ */
+ Timer(std::function<void()> userCallBack = nullptr) :
+ event(nullptr), eventSource(nullptr), expired(false),
+ userCallBack(userCallBack)
+ {
+ // take a reference to the default event object
+ sd_event_default(&event);
+ // Initialize the timer
+ initialize();
+ }
+
+ /** @brief Constructs timer object
+ *
+ * @param[in] events - sd_event pointer
+ * @param[in] funcCallBack - optional function callback for timer
+ * expirations
+ */
+ Timer(sd_event* event, std::function<void()> userCallBack = nullptr) :
+ event(event), eventSource(nullptr), expired(false),
+ userCallBack(userCallBack)
+ {
+ if (!event)
+ {
+ // take a reference to the default event object
+ sd_event_default(&event);
+ }
+ else
+ {
+ // take a reference to the event; the caller can
+ // keep their ref or drop it without harm
+ sd_event_ref(event);
+ }
+ // Initialize the timer
+ initialize();
+ }
+
+ ~Timer()
+ {
+ // the *_unref functions do nothing if passed nullptr
+ // so no need to check first
+ eventSource = sd_event_source_unref(eventSource);
+ event = sd_event_unref(event);
+ }
+
+ inline bool isExpired() const
+ {
+ return expired;
+ }
+
+ inline bool isRunning() const
+ {
+ int running = 0;
+ if (sd_event_source_get_enabled(eventSource, &running) < 0)
+ {
+ return false;
+ }
+ return running != SD_EVENT_OFF;
+ }
+
+ /** @brief Starts the timer with specified expiration value.
+ * input is an offset from the current steady_clock
+ */
+ int start(std::chrono::microseconds usec, bool periodic = false)
+ {
+ // Disable the timer
+ stop();
+ expired = false;
+ duration = usec;
+ if (periodic)
+ {
+ // A periodic timer means that when the timer goes off,
+ // it automatically rearms and starts running again
+ runType = SD_EVENT_ON;
+ }
+ else
+ {
+ // A ONESHOT timer means that when the timer goes off,
+ // it moves to disabled state.
+ runType = SD_EVENT_ONESHOT;
+ }
+
+ // Get the current MONOTONIC time and add the delta
+ auto expireTime = getTime() + usec;
+
+ // Set the time
+ int r = sd_event_source_set_time(eventSource, expireTime.count());
+ if (r < 0)
+ {
+ throw std::runtime_error("Failure to set timer");
+ }
+
+ r = setEnabled(runType);
+ if (r < 0)
+ {
+ throw std::runtime_error("Failure to start timer");
+ }
+ return r;
+ }
+
+ int stop()
+ {
+ return setEnabled(SD_EVENT_OFF);
+ }
+
+ private:
+ /** @brief the sd_event structure */
+ sd_event* event;
+
+ /** @brief Source of events */
+ sd_event_source* eventSource;
+
+ /** @brief Returns if the associated timer is expired
+ *
+ * This is set to true when the timeoutHandler is called into
+ */
+ bool expired;
+
+ /** @brief Optional function to call on timer expiration */
+ std::function<void()> userCallBack;
+
+ /** @brief timer duration */
+ std::chrono::microseconds duration;
+
+ /** @brief timer run type (oneshot or periodic) */
+ int runType;
+
+ /** @brief Initializes the timer object with infinite
+ * expiration time and sets up the callback handler
+ *
+ * @return None.
+ *
+ * @error std::runtime exception thrown
+ */
+ void initialize()
+ {
+ if (!event)
+ {
+ throw std::runtime_error("Timer has no event loop");
+ }
+ // This can not be called more than once.
+ if (eventSource)
+ {
+ throw std::runtime_error("Timer already initialized");
+ }
+
+ // Add infinite expiration time
+ auto r = sd_event_add_time(
+ event, &eventSource,
+ CLOCK_MONOTONIC, // Time base
+ UINT64_MAX, // Expire time - way long time
+ 0, // Use default event accuracy
+ [](sd_event_source* eventSource, uint64_t usec, void* userData) {
+ auto timer = static_cast<Timer*>(userData);
+ return timer->timeoutHandler();
+ }, // Callback handler on timeout
+ this); // User data
+ if (r < 0)
+ {
+ throw std::runtime_error("Timer initialization failed");
+ }
+
+ // Disable the timer for now
+ r = stop();
+ if (r < 0)
+ {
+ throw std::runtime_error("Disabling the timer failed");
+ }
+ }
+
+ /** @brief Enables / disables the timer */
+ int setEnabled(int action)
+ {
+ return sd_event_source_set_enabled(eventSource, action);
+ }
+
+ /** @brief Callback function when timer goes off
+ *
+ * On getting the signal, initiate the hard power off request
+ *
+ * @param[in] eventSource - Source of the event
+ * @param[in] usec - time in micro seconds
+ * @param[in] userData - User data pointer
+ *
+ */
+ int timeoutHandler()
+ {
+ if (runType == SD_EVENT_ON)
+ {
+ // set the event to the future
+ auto expireTime = getTime() + duration;
+
+ // Set the time
+ int r = sd_event_source_set_time(eventSource, expireTime.count());
+ if (r < 0)
+ {
+ throw std::runtime_error("Failure to set timer");
+ }
+ }
+ expired = true;
+
+ // Call optional user call back function if available
+ if (userCallBack)
+ {
+ userCallBack();
+ }
+
+ int running;
+ if (sd_event_source_get_enabled(eventSource, &running) < 0 ||
+ running == SD_EVENT_ONESHOT)
+ {
+ stop();
+ }
+ return 0;
+ }
+
+ /** @brief Gets the current time from steady clock */
+ static std::chrono::microseconds getTime()
+ {
+ using namespace std::chrono;
+ auto usec = steady_clock::now().time_since_epoch();
+ return duration_cast<microseconds>(usec);
+ }
+};
+
+} // namespace phosphor