1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef android_hardware_automotive_vehicle_V2_0_RecurrentTimer_H_ 18 #define android_hardware_automotive_vehicle_V2_0_RecurrentTimer_H_ 19 20 #include <atomic> 21 #include <chrono> 22 #include <condition_variable> 23 #include <functional> 24 #include <list> 25 #include <mutex> 26 #include <set> 27 #include <thread> 28 #include <unordered_map> 29 #include <vector> 30 31 /** 32 * This class allows to specify multiple time intervals to receive 33 * notifications. A single thread is used internally. 34 */ 35 class RecurrentTimer { 36 private: 37 using Nanos = std::chrono::nanoseconds; 38 using Clock = std::chrono::steady_clock; 39 using TimePoint = std::chrono::time_point<Clock, Nanos>; 40 public: 41 using Action = std::function<void(const std::vector<int32_t>& cookies)>; 42 RecurrentTimer(const Action & action)43 RecurrentTimer(const Action& action) : mAction(action) { 44 mTimerThread = std::thread(&RecurrentTimer::loop, this, action); 45 } 46 ~RecurrentTimer()47 virtual ~RecurrentTimer() { 48 stop(); 49 } 50 51 /** 52 * Registers recurrent event for a given interval. Registred events are distinguished by 53 * cookies thus calling this method multiple times with the same cookie will override the 54 * interval provided before. 55 */ registerRecurrentEvent(std::chrono::nanoseconds interval,int32_t cookie)56 void registerRecurrentEvent(std::chrono::nanoseconds interval, int32_t cookie) { 57 TimePoint now = Clock::now(); 58 // Align event time point among all intervals. Thus if we have two intervals 1ms and 2ms, 59 // during every second wake-up both intervals will be triggered. 60 TimePoint absoluteTime = now - Nanos(now.time_since_epoch().count() % interval.count()); 61 62 { 63 std::lock_guard<std::mutex> g(mLock); 64 mCookieToEventsMap[cookie] = { interval, cookie, absoluteTime }; 65 } 66 mCond.notify_one(); 67 } 68 unregisterRecurrentEvent(int32_t cookie)69 void unregisterRecurrentEvent(int32_t cookie) { 70 { 71 std::lock_guard<std::mutex> g(mLock); 72 mCookieToEventsMap.erase(cookie); 73 } 74 mCond.notify_one(); 75 } 76 77 78 private: 79 80 struct RecurrentEvent { 81 Nanos interval; 82 int32_t cookie; 83 TimePoint absoluteTime; // Absolute time of the next event. 84 updateNextEventTimeRecurrentEvent85 void updateNextEventTime(TimePoint now) { 86 // We want to move time to next event by adding some number of intervals (usually 1) 87 // to previous absoluteTime. 88 int intervalMultiplier = (now - absoluteTime) / interval; 89 if (intervalMultiplier <= 0) intervalMultiplier = 1; 90 absoluteTime += intervalMultiplier * interval; 91 } 92 }; 93 loop(const Action & action)94 void loop(const Action& action) { 95 static constexpr auto kInvalidTime = TimePoint(Nanos::max()); 96 97 std::vector<int32_t> cookies; 98 99 while (!mStopRequested) { 100 auto now = Clock::now(); 101 auto nextEventTime = kInvalidTime; 102 cookies.clear(); 103 104 { 105 std::unique_lock<std::mutex> g(mLock); 106 107 for (auto&& it : mCookieToEventsMap) { 108 RecurrentEvent& event = it.second; 109 if (event.absoluteTime <= now) { 110 event.updateNextEventTime(now); 111 cookies.push_back(event.cookie); 112 } 113 114 if (nextEventTime > event.absoluteTime) { 115 nextEventTime = event.absoluteTime; 116 } 117 } 118 } 119 120 if (cookies.size() != 0) { 121 action(cookies); 122 } 123 124 std::unique_lock<std::mutex> g(mLock); 125 mCond.wait_until(g, nextEventTime); // nextEventTime can be nanoseconds::max() 126 } 127 } 128 stop()129 void stop() { 130 mStopRequested = true; 131 { 132 std::lock_guard<std::mutex> g(mLock); 133 mCookieToEventsMap.clear(); 134 } 135 mCond.notify_one(); 136 if (mTimerThread.joinable()) { 137 mTimerThread.join(); 138 } 139 } 140 private: 141 mutable std::mutex mLock; 142 std::thread mTimerThread; 143 std::condition_variable mCond; 144 std::atomic_bool mStopRequested { false }; 145 Action mAction; 146 std::unordered_map<int32_t, RecurrentEvent> mCookieToEventsMap; 147 }; 148 149 150 #endif // android_hardware_automotive_vehicle_V2_0_RecurrentTimer_H 151