blob: 349bffdca68d51f9351167dea58fd41fa6bfb243 [file] [log] [blame]
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -08001/*
2 * Copyright (C) 2021 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
Andy Hunga2a1ac32022-03-18 16:12:11 -070019#include <atomic>
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080020#include <condition_variable>
Andy Hunga2a1ac32022-03-18 16:12:11 -070021#include <deque>
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080022#include <functional>
23#include <map>
24#include <mutex>
Andy Hunga2a1ac32022-03-18 16:12:11 -070025#include <string>
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080026#include <thread>
27
28#include <android-base/thread_annotations.h>
29
Andy Hungc8c2dde2022-07-15 15:18:59 -070030#include <mediautils/FixedString.h>
31
Andy Hunga2a1ac32022-03-18 16:12:11 -070032namespace android::mediautils {
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080033
34/**
35 * A thread for deferred execution of tasks, with cancellation.
36 */
37class TimerThread {
38 public:
Andy Hunga2a1ac32022-03-18 16:12:11 -070039 // A Handle is a time_point that serves as a unique key. It is ordered.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080040 using Handle = std::chrono::steady_clock::time_point;
41
Andy Hunga2a1ac32022-03-18 16:12:11 -070042 static inline constexpr Handle INVALID_HANDLE =
43 std::chrono::steady_clock::time_point::min();
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080044
45 /**
Andy Hunga2a1ac32022-03-18 16:12:11 -070046 * Schedules a task to be executed in the future (`timeout` duration from now).
47 *
48 * \param tag string associated with the task. This need not be unique,
49 * as the Handle returned is used for cancelling.
50 * \param func callback function that is invoked at the timeout.
51 * \param timeout timeout duration which is converted to milliseconds with at
52 * least 45 integer bits.
53 * A timeout of 0 (or negative) means the timer never expires
54 * so func() is never called. These tasks are stored internally
55 * and reported in the toString() until manually cancelled.
56 * \returns a handle that can be used for cancellation.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080057 */
Andy Hunga2a1ac32022-03-18 16:12:11 -070058 Handle scheduleTask(
Andy Hungc8c2dde2022-07-15 15:18:59 -070059 std::string_view tag, std::function<void()>&& func, std::chrono::milliseconds timeout);
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080060
61 /**
Andy Hunga2a1ac32022-03-18 16:12:11 -070062 * Tracks a task that shows up on toString() until cancelled.
63 *
64 * \param tag string associated with the task.
65 * \returns a handle that can be used for cancellation.
66 */
Andy Hungc8c2dde2022-07-15 15:18:59 -070067 Handle trackTask(std::string_view tag);
Andy Hunga2a1ac32022-03-18 16:12:11 -070068
69 /**
70 * Cancels a task previously scheduled with scheduleTask()
71 * or trackTask().
72 *
73 * \returns true if cancelled. If the task has already executed
74 * or if the handle doesn't exist, this is a no-op
75 * and returns false.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080076 */
Andy Hung5c6d68a2022-03-09 21:54:59 -080077 bool cancelTask(Handle handle);
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080078
Andy Hunga2a1ac32022-03-18 16:12:11 -070079 std::string toString(size_t retiredCount = SIZE_MAX) const;
80
81 /**
82 * Returns a string representation of the TimerThread queue.
83 *
84 * The queue is dumped in order of scheduling (not deadline).
85 */
86 std::string pendingToString() const;
87
88 /**
89 * Returns a string representation of the last retired tasks.
90 *
91 * These tasks from trackTask() or scheduleTask() are
92 * cancelled.
93 *
94 * These are ordered when the task was retired.
95 *
96 * \param n is maximum number of tasks to dump.
97 */
98 std::string retiredToString(size_t n = SIZE_MAX) const;
99
100
101 /**
102 * Returns a string representation of the last timeout tasks.
103 *
104 * These tasks from scheduleTask() which have timed-out.
105 *
106 * These are ordered when the task had timed-out.
107 *
108 * \param n is maximum number of tasks to dump.
109 */
110 std::string timeoutToString(size_t n = SIZE_MAX) const;
111
112 /**
113 * Dumps a container with SmartPointer<Request> to a string.
114 *
115 * "{ Request1 } { Request2} ...{ RequestN }"
116 */
117 template <typename T>
118 static std::string requestsToString(const T& containerRequests) {
119 std::string s;
120 // append seems to be faster than stringstream.
121 // https://stackoverflow.com/questions/18892281/most-optimized-way-of-concatenation-in-strings
122 for (const auto& request : containerRequests) {
123 s.append("{ ").append(request->toString()).append(" } ");
124 }
125 // If not empty, there's an extra space at the end, so we trim it off.
126 if (!s.empty()) s.pop_back();
127 return s;
128 }
129
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800130 private:
Andy Hunga2a1ac32022-03-18 16:12:11 -0700131 // To minimize movement of data, we pass around shared_ptrs to Requests.
132 // These are allocated and deallocated outside of the lock.
133 struct Request {
Andy Hungc8c2dde2022-07-15 15:18:59 -0700134 Request(const std::chrono::system_clock::time_point& _scheduled,
135 const std::chrono::system_clock::time_point& _deadline,
136 pid_t _tid,
137 std::string_view _tag)
138 : scheduled(_scheduled)
139 , deadline(_deadline)
140 , tid(_tid)
141 , tag(_tag)
142 {}
143
Andy Hunga2a1ac32022-03-18 16:12:11 -0700144 const std::chrono::system_clock::time_point scheduled;
145 const std::chrono::system_clock::time_point deadline; // deadline := scheduled + timeout
146 // if deadline == scheduled, no
147 // timeout, task not executed.
148 const pid_t tid;
Andy Hungc8c2dde2022-07-15 15:18:59 -0700149 const FixedString62 tag;
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800150
Andy Hunga2a1ac32022-03-18 16:12:11 -0700151 std::string toString() const;
152 };
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800153
Andy Hunga2a1ac32022-03-18 16:12:11 -0700154 // Deque of requests, in order of add().
155 // This class is thread-safe.
156 class RequestQueue {
157 public:
158 explicit RequestQueue(size_t maxSize)
159 : mRequestQueueMax(maxSize) {}
160
161 void add(std::shared_ptr<const Request>);
162
163 // return up to the last "n" requests retired.
164 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests,
165 size_t n = SIZE_MAX) const;
166
167 private:
168 const size_t mRequestQueueMax;
169 mutable std::mutex mRQMutex;
170 std::deque<std::pair<std::chrono::system_clock::time_point,
171 std::shared_ptr<const Request>>>
172 mRequestQueue GUARDED_BY(mRQMutex);
173 };
174
175 // A storage map of tasks without timeouts. There is no std::function<void()>
176 // required, it just tracks the tasks with the tag, scheduled time and the tid.
177 // These tasks show up on a pendingToString() until manually cancelled.
178 class NoTimeoutMap {
179 // This a counter of the requests that have no timeout (timeout == 0).
180 std::atomic<size_t> mNoTimeoutRequests{};
181
182 mutable std::mutex mNTMutex;
183 std::map<Handle, std::shared_ptr<const Request>> mMap GUARDED_BY(mNTMutex);
184
185 public:
186 bool isValidHandle(Handle handle) const; // lock free
187 Handle add(std::shared_ptr<const Request> request);
188 std::shared_ptr<const Request> remove(Handle handle);
189 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
190 };
191
192 // Monitor thread.
193 // This thread manages shared pointers to Requests and a function to
194 // call on timeout.
195 // This class is thread-safe.
196 class MonitorThread {
197 mutable std::mutex mMutex;
198 mutable std::condition_variable mCond;
199
200 // Ordered map of requests based on time of deadline.
201 //
202 std::map<Handle, std::pair<std::shared_ptr<const Request>, std::function<void()>>>
203 mMonitorRequests GUARDED_BY(mMutex);
204
205 RequestQueue& mTimeoutQueue; // locked internally, added to when request times out.
206
207 // Worker thread variables
208 bool mShouldExit GUARDED_BY(mMutex) = false;
209
210 // To avoid race with initialization,
211 // mThread should be initialized last as the thread is launched immediately.
212 std::thread mThread;
213
214 void threadFunc();
215 Handle getUniqueHandle_l(std::chrono::milliseconds timeout) REQUIRES(mMutex);
216
217 public:
218 MonitorThread(RequestQueue &timeoutQueue);
219 ~MonitorThread();
220
221 Handle add(std::shared_ptr<const Request> request, std::function<void()>&& func,
222 std::chrono::milliseconds timeout);
223 std::shared_ptr<const Request> remove(Handle handle);
224 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
225 };
226
Andy Hungf45f34c2022-03-25 13:09:03 -0700227 // Analysis contains info deduced by analysisTimeout().
228 //
229 // Summary is the result string from checking timeoutRequests to see if
230 // any might be caused by blocked calls in pendingRequests.
231 //
232 // Summary string is empty if there is no automatic actionable info.
233 //
234 // timeoutTid is the tid selected from timeoutRequests (if any).
235 //
236 // HALBlockedTid is the tid that is blocked from pendingRequests believed
237 // to cause the timeout.
238 // HALBlockedTid may be INVALID_PID if no suspected tid is found,
239 // and if HALBlockedTid is valid, it will not be the same as timeoutTid.
240 //
241 static constexpr pid_t INVALID_PID = -1;
242 struct Analysis {
243 std::string summary;
244 pid_t timeoutTid = INVALID_PID;
245 pid_t HALBlockedTid = INVALID_PID;
246 };
247
248 // A HAL method is where the substring "Hidl" is in the class name.
249 // The tag should look like: ... Hidl ... :: ...
250 static bool isRequestFromHal(const std::shared_ptr<const Request>& request);
251
252 // Returns analysis from the requests.
253 static Analysis analyzeTimeout(
254 const std::vector<std::shared_ptr<const Request>>& timeoutRequests,
255 const std::vector<std::shared_ptr<const Request>>& pendingRequests);
256
Andy Hunga2a1ac32022-03-18 16:12:11 -0700257 std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
258
259 // A no-timeout request is represented by a handles at the end of steady_clock time,
260 // counting down by the number of no timeout requests previously requested.
261 // We manage them on the NoTimeoutMap, but conceptually they could be scheduled
262 // on the MonitorThread because those time handles won't expire in
263 // the lifetime of the device.
264 static inline Handle getIndexedHandle(size_t index) {
265 return std::chrono::time_point<std::chrono::steady_clock>::max() -
266 std::chrono::time_point<std::chrono::steady_clock>::duration(index);
267 }
268
269 static constexpr size_t kRetiredQueueMax = 16;
270 RequestQueue mRetiredQueue{kRetiredQueueMax}; // locked internally
271
272 static constexpr size_t kTimeoutQueueMax = 16;
273 RequestQueue mTimeoutQueue{kTimeoutQueueMax}; // locked internally
274
275 NoTimeoutMap mNoTimeoutMap; // locked internally
276
277 MonitorThread mMonitorThread{mTimeoutQueue}; // This should be initialized last because
278 // the thread is launched immediately.
279 // Locked internally.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800280};
281
Andy Hunga2a1ac32022-03-18 16:12:11 -0700282} // namespace android::mediautils