xref: /aosp_15_r20/frameworks/av/services/mediametrics/include/mediametricsservice/TimedAction.h (revision ec779b8e0859a360c3d303172224686826e6e0e1)
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     // Use system_clock instead of steady_clock to include suspend time.
29     using TimerClock = class std::chrono::system_clock;
30 
31     // Define granularity of wakeup to prevent delayed events if
32     // device is suspended.
33     static constexpr auto kWakeupInterval = std::chrono::minutes(3);
34 public:
TimedAction()35     TimedAction() : mThread{[this](){threadLoop();}} {}
36 
~TimedAction()37     ~TimedAction() {
38         quit();
39     }
40 
41     // TODO: return a handle for cancelling the action?
42     template <typename T> // T is in units of std::chrono::duration.
postIn(const T & time,std::function<void ()> f)43     void postIn(const T& time, std::function<void()> f) {
44         postAt(TimerClock::now() + time, f);
45     }
46 
47     template <typename T> // T is in units of std::chrono::time_point
postAt(const T & targetTime,std::function<void ()> f)48     void postAt(const T& targetTime, std::function<void()> f) {
49         std::lock_guard l(mLock);
50         if (mQuit) return;
51         if (mMap.empty() || targetTime < mMap.begin()->first) {
52             mMap.emplace_hint(mMap.begin(), targetTime, std::move(f));
53             mCondition.notify_one();
54         } else {
55             mMap.emplace(targetTime, std::move(f));
56         }
57     }
58 
clear()59     void clear() {
60         std::lock_guard l(mLock);
61         mMap.clear();
62     }
63 
quit()64     void quit() {
65         {
66             std::lock_guard l(mLock);
67             if (mQuit) return;
68             mQuit = true;
69             mMap.clear();
70             mCondition.notify_all();
71         }
72         mThread.join();
73     }
74 
size()75     size_t size() const {
76         std::lock_guard l(mLock);
77         return mMap.size();
78     }
79 
80 private:
threadLoop()81     void threadLoop() NO_THREAD_SAFETY_ANALYSIS { // thread safety doesn't cover unique_lock
82         std::unique_lock l(mLock);
83         while (!mQuit) {
84             if (!mMap.empty()) {
85                 auto sleepUntilTime = mMap.begin()->first;
86                 const auto now = TimerClock::now();
87                 if (sleepUntilTime <= now) {
88                     auto node = mMap.extract(mMap.begin()); // removes from mMap.
89                     l.unlock();
90                     node.mapped()();
91                     l.lock();
92                     continue;
93                 }
94                 // Bionic uses CLOCK_MONOTONIC for its pthread_mutex regardless
95                 // of REALTIME specification, use kWakeupInterval to ensure minimum
96                 // granularity if suspended.
97                 sleepUntilTime = std::min(sleepUntilTime, now + kWakeupInterval);
98                 mCondition.wait_until(l, sleepUntilTime);
99             } else {
100                 // As TimerClock is system_clock (which is not monotonic), libcxx's
101                 // implementation of condition_variable::wait_until(l, std::chrono::time_point)
102                 // recalculates the 'until' time into the wait duration and then goes back to the
103                 // absolute timestamp when calling pthread_cond_timedwait(); this back-and-forth
104                 // calculation sometimes loses the 'max' value because enough time passes in
105                 // between, and instead passes incorrect timestamp into the syscall, causing a
106                 // crash. Mitigating it by explicitly calling the non-timed wait here.
107                 mCondition.wait(l);
108             }
109         }
110     }
111 
112     mutable std::mutex mLock;
113     std::condition_variable mCondition GUARDED_BY(mLock);
114     bool mQuit GUARDED_BY(mLock) = false;
115     std::multimap<std::chrono::time_point<TimerClock>, std::function<void()>>
116             mMap GUARDED_BY(mLock); // multiple functions could execute at the same time.
117 
118     // needs to be initialized after the variables above, done in constructor initializer list.
119     std::thread mThread;
120 };
121 
122 } // namespace android::mediametrics
123