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