1 /*
2  * Copyright (C) 2020 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 #pragma once
18 
19 #include <android-base/thread_annotations.h>
20 #include <chrono>
21 #include <map>
22 #include <mutex>
23 #include <thread>
24 
25 namespace android::mediametrics {
26 
27 class TimedAction {
28 public:
TimedAction()29     TimedAction() : mThread{[this](){threadLoop();}} {}
30 
~TimedAction()31     ~TimedAction() {
32         quit();
33     }
34 
35     // TODO: return a handle for cancelling the action?
36     template <typename T> // T is in units of std::chrono::duration.
postIn(const T & time,std::function<void ()> f)37     void postIn(const T& time, std::function<void()> f) {
38         postAt(std::chrono::steady_clock::now() + time, f);
39     }
40 
41     template <typename T> // T is in units of std::chrono::time_point
postAt(const T & targetTime,std::function<void ()> f)42     void postAt(const T& targetTime, std::function<void()> f) {
43         std::lock_guard l(mLock);
44         if (mQuit) return;
45         if (mMap.empty() || targetTime < mMap.begin()->first) {
46             mMap.emplace_hint(mMap.begin(), targetTime, std::move(f));
47             mCondition.notify_one();
48         } else {
49             mMap.emplace(targetTime, std::move(f));
50         }
51     }
52 
clear()53     void clear() {
54         std::lock_guard l(mLock);
55         mMap.clear();
56     }
57 
quit()58     void quit() {
59         {
60             std::lock_guard l(mLock);
61             if (mQuit) return;
62             mQuit = true;
63             mMap.clear();
64             mCondition.notify_all();
65         }
66         mThread.join();
67     }
68 
size()69     size_t size() const {
70         std::lock_guard l(mLock);
71         return mMap.size();
72     }
73 
74 private:
threadLoop()75     void threadLoop() NO_THREAD_SAFETY_ANALYSIS { // thread safety doesn't cover unique_lock
76         std::unique_lock l(mLock);
77         while (!mQuit) {
78             auto sleepUntilTime = std::chrono::time_point<std::chrono::steady_clock>::max();
79             if (!mMap.empty()) {
80                 sleepUntilTime = mMap.begin()->first;
81                 if (sleepUntilTime <= std::chrono::steady_clock::now()) {
82                     auto node = mMap.extract(mMap.begin()); // removes from mMap.
83                     l.unlock();
84                     node.mapped()();
85                     l.lock();
86                     continue;
87                 }
88             }
89             mCondition.wait_until(l, sleepUntilTime);
90         }
91     }
92 
93     mutable std::mutex mLock;
94     std::condition_variable mCondition GUARDED_BY(mLock);
95     bool mQuit GUARDED_BY(mLock) = false;
96     std::multimap<std::chrono::time_point<std::chrono::steady_clock>, std::function<void()>>
97             mMap GUARDED_BY(mLock); // multiple functions could execute at the same time.
98 
99     // needs to be initialized after the variables above, done in constructor initializer list.
100     std::thread mThread;
101 };
102 
103 } // namespace android::mediametrics
104