1 #pragma once 2 3 #include <systemd/sd-event.h> 4 5 #include <chrono> 6 #include <functional> 7 #include <stdexcept> 8 9 namespace sdbusplus 10 { 11 12 /** @class Timer 13 * @brief Manages starting watchdog timers and handling timeouts 14 */ 15 class Timer 16 { 17 public: 18 /** @brief Only need the default Timer */ 19 Timer() = delete; 20 Timer(const Timer&) = delete; 21 Timer& operator=(const Timer&) = delete; 22 Timer(Timer&&) = delete; 23 Timer& operator=(Timer&&) = delete; 24 25 /** @brief Constructs timer object 26 * Uses the default sd_event object 27 * 28 * @param[in] userCallBack - optional function callback for timer 29 * expirations 30 */ Timer(std::function<void ()> userCallBack=nullptr)31 Timer(std::function<void()> userCallBack = nullptr) : 32 event(nullptr), eventSource(nullptr), expired(false), 33 userCallBack(userCallBack) 34 { 35 // take a reference to the default event object 36 sd_event_default(&event); 37 // Initialize the timer 38 initialize(); 39 } 40 41 /** @brief Constructs timer object 42 * 43 * @param[in] event - sd_event pointer 44 * @param[in] userCallBack - optional function callback for timer 45 * expirations 46 */ Timer(sd_event * event,std::function<void ()> userCallBack=nullptr)47 Timer(sd_event* event, std::function<void()> userCallBack = nullptr) : 48 event(event), eventSource(nullptr), expired(false), 49 userCallBack(userCallBack) 50 { 51 if (!event) 52 { 53 // take a reference to the default event object 54 sd_event_default(&event); 55 } 56 else 57 { 58 // take a reference to the event; the caller can 59 // keep their ref or drop it without harm 60 sd_event_ref(event); 61 } 62 // Initialize the timer 63 initialize(); 64 } 65 ~Timer()66 ~Timer() 67 { 68 // the *_unref functions do nothing if passed nullptr 69 // so no need to check first 70 eventSource = sd_event_source_unref(eventSource); 71 event = sd_event_unref(event); 72 } 73 isExpired() const74 inline bool isExpired() const 75 { 76 return expired; 77 } 78 isRunning() const79 inline bool isRunning() const 80 { 81 int running = 0; 82 if (sd_event_source_get_enabled(eventSource, &running) < 0) 83 { 84 return false; 85 } 86 return running != SD_EVENT_OFF; 87 } 88 89 /** @brief Starts the timer with specified expiration value. 90 * input is an offset from the current steady_clock 91 */ start(std::chrono::microseconds usec,bool periodic=false)92 int start(std::chrono::microseconds usec, bool periodic = false) 93 { 94 // Disable the timer 95 stop(); 96 expired = false; 97 duration = usec; 98 if (periodic) 99 { 100 // A periodic timer means that when the timer goes off, 101 // it automatically rearms and starts running again 102 runType = SD_EVENT_ON; 103 } 104 else 105 { 106 // A ONESHOT timer means that when the timer goes off, 107 // it moves to disabled state. 108 runType = SD_EVENT_ONESHOT; 109 } 110 111 // Get the current MONOTONIC time and add the delta 112 auto expireTime = getTime() + usec; 113 114 // Set the time 115 int r = sd_event_source_set_time(eventSource, expireTime.count()); 116 if (r < 0) 117 { 118 throw std::runtime_error("Failure to set timer"); 119 } 120 121 r = setEnabled(runType); 122 if (r < 0) 123 { 124 throw std::runtime_error("Failure to start timer"); 125 } 126 return r; 127 } 128 stop()129 int stop() 130 { 131 return setEnabled(SD_EVENT_OFF); 132 } 133 134 private: 135 /** @brief the sd_event structure */ 136 sd_event* event; 137 138 /** @brief Source of events */ 139 sd_event_source* eventSource; 140 141 /** @brief Returns if the associated timer is expired 142 * 143 * This is set to true when the timeoutHandler is called into 144 */ 145 bool expired; 146 147 /** @brief Optional function to call on timer expiration */ 148 std::function<void()> userCallBack; 149 150 /** @brief timer duration */ 151 std::chrono::microseconds duration; 152 153 /** @brief timer run type (oneshot or periodic) */ 154 int runType; 155 156 /** @brief Initializes the timer object with infinite 157 * expiration time and sets up the callback handler 158 * 159 * @error std::runtime exception thrown 160 */ initialize()161 void initialize() 162 { 163 if (!event) 164 { 165 throw std::runtime_error("Timer has no event loop"); 166 } 167 // This can not be called more than once. 168 if (eventSource) 169 { 170 throw std::runtime_error("Timer already initialized"); 171 } 172 173 // Add infinite expiration time 174 auto r = sd_event_add_time( 175 event, &eventSource, 176 CLOCK_MONOTONIC, // Time base 177 UINT64_MAX, // Expire time - way long time 178 0, // Use default event accuracy 179 [](sd_event_source* /*eventSource*/, uint64_t /*usec*/, 180 void* userData) { 181 auto timer = static_cast<Timer*>(userData); 182 return timer->timeoutHandler(); 183 }, // Callback handler on timeout 184 this); // User data 185 if (r < 0) 186 { 187 throw std::runtime_error("Timer initialization failed"); 188 } 189 190 // Disable the timer for now 191 r = stop(); 192 if (r < 0) 193 { 194 throw std::runtime_error("Disabling the timer failed"); 195 } 196 } 197 198 /** @brief Enables / disables the timer */ setEnabled(int action)199 int setEnabled(int action) 200 { 201 return sd_event_source_set_enabled(eventSource, action); 202 } 203 204 /** @brief Callback function when timer goes off 205 * 206 * On getting the signal, initiate the hard power off request 207 * 208 */ timeoutHandler()209 int timeoutHandler() 210 { 211 if (runType == SD_EVENT_ON) 212 { 213 // set the event to the future 214 auto expireTime = getTime() + duration; 215 216 // Set the time 217 int r = sd_event_source_set_time(eventSource, expireTime.count()); 218 if (r < 0) 219 { 220 throw std::runtime_error("Failure to set timer"); 221 } 222 } 223 expired = true; 224 225 // Call optional user call back function if available 226 if (userCallBack) 227 { 228 userCallBack(); 229 } 230 231 int running; 232 if (sd_event_source_get_enabled(eventSource, &running) < 0 || 233 running == SD_EVENT_ONESHOT) 234 { 235 stop(); 236 } 237 return 0; 238 } 239 240 /** @brief Gets the current time from steady clock */ getTime()241 static std::chrono::microseconds getTime() 242 { 243 using namespace std::chrono; 244 auto usec = steady_clock::now().time_since_epoch(); 245 return duration_cast<microseconds>(usec); 246 } 247 }; 248 249 } // namespace sdbusplus 250