blob: ff3ef4bd5e2d21d7a0fda47d4f88e548b35d08b6 [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 Hungdf1ed5c2022-06-13 19:49:43 -070039 // A Handle is a time_point that serves as a unique key to access a queued
40 // request to the TimerThread.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080041 using Handle = std::chrono::steady_clock::time_point;
42
Andy Hungdf1ed5c2022-06-13 19:49:43 -070043 // Duration is based on steady_clock (typically nanoseconds)
44 // vs the system_clock duration (typically microseconds).
45 using Duration = std::chrono::steady_clock::duration;
46
Andy Hunga2a1ac32022-03-18 16:12:11 -070047 static inline constexpr Handle INVALID_HANDLE =
48 std::chrono::steady_clock::time_point::min();
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -080049
Andy Hungdf1ed5c2022-06-13 19:49:43 -070050 // Handle implementation details:
51 // A Handle represents the timer expiration time based on std::chrono::steady_clock
52 // (clock monotonic). This Handle is computed as now() + timeout.
53 //
54 // The lsb of the Handle time_point is adjusted to indicate whether there is
55 // a timeout action (1) or not (0).
56 //
57
58 template <size_t COUNT>
59 static constexpr bool is_power_of_2_v = COUNT > 0 && (COUNT & (COUNT - 1)) == 0;
60
61 template <size_t COUNT>
62 static constexpr size_t mask_from_count_v = COUNT - 1;
63
64 static constexpr size_t HANDLE_TYPES = 2;
65 // HANDLE_TYPES must be a power of 2.
66 static_assert(is_power_of_2_v<HANDLE_TYPES>);
67
68 // The handle types
69 enum class HANDLE_TYPE : size_t {
70 NO_TIMEOUT = 0,
71 TIMEOUT = 1,
72 };
73
74 static constexpr size_t HANDLE_TYPE_MASK = mask_from_count_v<HANDLE_TYPES>;
75
76 template <typename T>
77 static constexpr auto enum_as_value(T x) {
78 return static_cast<std::underlying_type_t<T>>(x);
79 }
80
81 static inline bool isNoTimeoutHandle(Handle handle) {
82 return (handle.time_since_epoch().count() & HANDLE_TYPE_MASK) ==
83 enum_as_value(HANDLE_TYPE::NO_TIMEOUT);
84 }
85
86 static inline bool isTimeoutHandle(Handle handle) {
87 return (handle.time_since_epoch().count() & HANDLE_TYPE_MASK) ==
88 enum_as_value(HANDLE_TYPE::TIMEOUT);
89 }
90
91 // Returns a unique Handle that doesn't exist in the container.
92 template <size_t MAX_TYPED_HANDLES, size_t HANDLE_TYPE_AS_VALUE, typename C, typename T>
93 static Handle getUniqueHandleForHandleType_l(C container, T timeout) {
94 static_assert(MAX_TYPED_HANDLES > 0 && HANDLE_TYPE_AS_VALUE < MAX_TYPED_HANDLES
95 && is_power_of_2_v<MAX_TYPED_HANDLES>,
96 " handles must be power of two");
97
98 // Our initial handle is the deadline as computed from steady_clock.
99 auto deadline = std::chrono::steady_clock::now() + timeout;
100
101 // We adjust the lsbs by the minimum increment to have the correct
102 // HANDLE_TYPE in the least significant bits.
103 auto remainder = deadline.time_since_epoch().count() & HANDLE_TYPE_MASK;
104 size_t offset = HANDLE_TYPE_AS_VALUE > remainder ? HANDLE_TYPE_AS_VALUE - remainder :
105 MAX_TYPED_HANDLES + HANDLE_TYPE_AS_VALUE - remainder;
106 deadline += std::chrono::steady_clock::duration(offset);
107
108 // To avoid key collisions, advance the handle by MAX_TYPED_HANDLES (the modulus factor)
109 // until the key is unique.
110 while (container.find(deadline) != container.end()) {
111 deadline += std::chrono::steady_clock::duration(MAX_TYPED_HANDLES);
112 }
113 return deadline;
114 }
115
116 // TimerCallback invoked on timeout or cancel.
117 using TimerCallback = std::function<void(Handle)>;
118
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800119 /**
Andy Hunga2a1ac32022-03-18 16:12:11 -0700120 * Schedules a task to be executed in the future (`timeout` duration from now).
121 *
122 * \param tag string associated with the task. This need not be unique,
123 * as the Handle returned is used for cancelling.
124 * \param func callback function that is invoked at the timeout.
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700125 * \param timeoutDuration timeout duration which is converted to milliseconds with at
Andy Hunga2a1ac32022-03-18 16:12:11 -0700126 * least 45 integer bits.
127 * A timeout of 0 (or negative) means the timer never expires
128 * so func() is never called. These tasks are stored internally
129 * and reported in the toString() until manually cancelled.
130 * \returns a handle that can be used for cancellation.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800131 */
Andy Hunga2a1ac32022-03-18 16:12:11 -0700132 Handle scheduleTask(
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700133 std::string_view tag, TimerCallback&& func, Duration timeoutDuration);
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800134
135 /**
Andy Hunga2a1ac32022-03-18 16:12:11 -0700136 * Tracks a task that shows up on toString() until cancelled.
137 *
138 * \param tag string associated with the task.
139 * \returns a handle that can be used for cancellation.
140 */
Andy Hungc8c2dde2022-07-15 15:18:59 -0700141 Handle trackTask(std::string_view tag);
Andy Hunga2a1ac32022-03-18 16:12:11 -0700142
143 /**
144 * Cancels a task previously scheduled with scheduleTask()
145 * or trackTask().
146 *
147 * \returns true if cancelled. If the task has already executed
148 * or if the handle doesn't exist, this is a no-op
149 * and returns false.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800150 */
Andy Hung5c6d68a2022-03-09 21:54:59 -0800151 bool cancelTask(Handle handle);
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800152
Andy Hunga2a1ac32022-03-18 16:12:11 -0700153 std::string toString(size_t retiredCount = SIZE_MAX) const;
154
155 /**
156 * Returns a string representation of the TimerThread queue.
157 *
158 * The queue is dumped in order of scheduling (not deadline).
159 */
160 std::string pendingToString() const;
161
162 /**
163 * Returns a string representation of the last retired tasks.
164 *
165 * These tasks from trackTask() or scheduleTask() are
166 * cancelled.
167 *
168 * These are ordered when the task was retired.
169 *
170 * \param n is maximum number of tasks to dump.
171 */
172 std::string retiredToString(size_t n = SIZE_MAX) const;
173
174
175 /**
176 * Returns a string representation of the last timeout tasks.
177 *
178 * These tasks from scheduleTask() which have timed-out.
179 *
180 * These are ordered when the task had timed-out.
181 *
182 * \param n is maximum number of tasks to dump.
183 */
184 std::string timeoutToString(size_t n = SIZE_MAX) const;
185
186 /**
187 * Dumps a container with SmartPointer<Request> to a string.
188 *
189 * "{ Request1 } { Request2} ...{ RequestN }"
190 */
191 template <typename T>
192 static std::string requestsToString(const T& containerRequests) {
193 std::string s;
194 // append seems to be faster than stringstream.
195 // https://stackoverflow.com/questions/18892281/most-optimized-way-of-concatenation-in-strings
196 for (const auto& request : containerRequests) {
197 s.append("{ ").append(request->toString()).append(" } ");
198 }
199 // If not empty, there's an extra space at the end, so we trim it off.
200 if (!s.empty()) s.pop_back();
201 return s;
202 }
203
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800204 private:
Andy Hunga2a1ac32022-03-18 16:12:11 -0700205 // To minimize movement of data, we pass around shared_ptrs to Requests.
206 // These are allocated and deallocated outside of the lock.
207 struct Request {
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700208 Request(std::chrono::system_clock::time_point _scheduled,
209 std::chrono::system_clock::time_point _deadline,
Andy Hungc8c2dde2022-07-15 15:18:59 -0700210 pid_t _tid,
211 std::string_view _tag)
212 : scheduled(_scheduled)
213 , deadline(_deadline)
214 , tid(_tid)
215 , tag(_tag)
216 {}
217
Andy Hunga2a1ac32022-03-18 16:12:11 -0700218 const std::chrono::system_clock::time_point scheduled;
219 const std::chrono::system_clock::time_point deadline; // deadline := scheduled + timeout
220 // if deadline == scheduled, no
221 // timeout, task not executed.
222 const pid_t tid;
Andy Hungc8c2dde2022-07-15 15:18:59 -0700223 const FixedString62 tag;
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800224
Andy Hunga2a1ac32022-03-18 16:12:11 -0700225 std::string toString() const;
226 };
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800227
Andy Hunga2a1ac32022-03-18 16:12:11 -0700228 // Deque of requests, in order of add().
229 // This class is thread-safe.
230 class RequestQueue {
231 public:
232 explicit RequestQueue(size_t maxSize)
233 : mRequestQueueMax(maxSize) {}
234
235 void add(std::shared_ptr<const Request>);
236
237 // return up to the last "n" requests retired.
238 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests,
239 size_t n = SIZE_MAX) const;
240
241 private:
242 const size_t mRequestQueueMax;
243 mutable std::mutex mRQMutex;
244 std::deque<std::pair<std::chrono::system_clock::time_point,
245 std::shared_ptr<const Request>>>
246 mRequestQueue GUARDED_BY(mRQMutex);
247 };
248
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700249 // A storage map of tasks without timeouts. There is no TimerCallback
Andy Hunga2a1ac32022-03-18 16:12:11 -0700250 // required, it just tracks the tasks with the tag, scheduled time and the tid.
251 // These tasks show up on a pendingToString() until manually cancelled.
252 class NoTimeoutMap {
Andy Hunga2a1ac32022-03-18 16:12:11 -0700253 mutable std::mutex mNTMutex;
254 std::map<Handle, std::shared_ptr<const Request>> mMap GUARDED_BY(mNTMutex);
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700255 Handle getUniqueHandle_l() REQUIRES(mNTMutex) {
256 return getUniqueHandleForHandleType_l<
257 HANDLE_TYPES, enum_as_value(HANDLE_TYPE::NO_TIMEOUT)>(
258 mMap, Duration{} /* timeout */);
259 }
Andy Hunga2a1ac32022-03-18 16:12:11 -0700260
261 public:
262 bool isValidHandle(Handle handle) const; // lock free
263 Handle add(std::shared_ptr<const Request> request);
264 std::shared_ptr<const Request> remove(Handle handle);
265 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
266 };
267
268 // Monitor thread.
269 // This thread manages shared pointers to Requests and a function to
270 // call on timeout.
271 // This class is thread-safe.
272 class MonitorThread {
273 mutable std::mutex mMutex;
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700274 mutable std::condition_variable mCond GUARDED_BY(mMutex);
Andy Hunga2a1ac32022-03-18 16:12:11 -0700275
276 // Ordered map of requests based on time of deadline.
277 //
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700278 std::map<Handle, std::pair<std::shared_ptr<const Request>, TimerCallback>>
Andy Hunga2a1ac32022-03-18 16:12:11 -0700279 mMonitorRequests GUARDED_BY(mMutex);
280
281 RequestQueue& mTimeoutQueue; // locked internally, added to when request times out.
282
283 // Worker thread variables
284 bool mShouldExit GUARDED_BY(mMutex) = false;
285
286 // To avoid race with initialization,
287 // mThread should be initialized last as the thread is launched immediately.
288 std::thread mThread;
289
290 void threadFunc();
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700291 Handle getUniqueHandle_l(Duration timeout) REQUIRES(mMutex) {
292 return getUniqueHandleForHandleType_l<
293 HANDLE_TYPES, enum_as_value(HANDLE_TYPE::TIMEOUT)>(
294 mMonitorRequests, timeout);
295 }
Andy Hunga2a1ac32022-03-18 16:12:11 -0700296
297 public:
298 MonitorThread(RequestQueue &timeoutQueue);
299 ~MonitorThread();
300
Andy Hungdf1ed5c2022-06-13 19:49:43 -0700301 Handle add(std::shared_ptr<const Request> request, TimerCallback&& func,
302 Duration timeout);
Andy Hunga2a1ac32022-03-18 16:12:11 -0700303 std::shared_ptr<const Request> remove(Handle handle);
304 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
305 };
306
Andy Hungf45f34c2022-03-25 13:09:03 -0700307 // Analysis contains info deduced by analysisTimeout().
308 //
309 // Summary is the result string from checking timeoutRequests to see if
310 // any might be caused by blocked calls in pendingRequests.
311 //
312 // Summary string is empty if there is no automatic actionable info.
313 //
314 // timeoutTid is the tid selected from timeoutRequests (if any).
315 //
316 // HALBlockedTid is the tid that is blocked from pendingRequests believed
317 // to cause the timeout.
318 // HALBlockedTid may be INVALID_PID if no suspected tid is found,
319 // and if HALBlockedTid is valid, it will not be the same as timeoutTid.
320 //
321 static constexpr pid_t INVALID_PID = -1;
322 struct Analysis {
323 std::string summary;
324 pid_t timeoutTid = INVALID_PID;
325 pid_t HALBlockedTid = INVALID_PID;
326 };
327
328 // A HAL method is where the substring "Hidl" is in the class name.
329 // The tag should look like: ... Hidl ... :: ...
330 static bool isRequestFromHal(const std::shared_ptr<const Request>& request);
331
332 // Returns analysis from the requests.
333 static Analysis analyzeTimeout(
334 const std::vector<std::shared_ptr<const Request>>& timeoutRequests,
335 const std::vector<std::shared_ptr<const Request>>& pendingRequests);
336
Andy Hunga2a1ac32022-03-18 16:12:11 -0700337 std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
338
Andy Hunga2a1ac32022-03-18 16:12:11 -0700339 static constexpr size_t kRetiredQueueMax = 16;
340 RequestQueue mRetiredQueue{kRetiredQueueMax}; // locked internally
341
342 static constexpr size_t kTimeoutQueueMax = 16;
343 RequestQueue mTimeoutQueue{kTimeoutQueueMax}; // locked internally
344
345 NoTimeoutMap mNoTimeoutMap; // locked internally
346
347 MonitorThread mMonitorThread{mTimeoutQueue}; // This should be initialized last because
348 // the thread is launched immediately.
349 // Locked internally.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800350};
351
Andy Hunga2a1ac32022-03-18 16:12:11 -0700352} // namespace android::mediautils