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