blob: c76fa7da3d68db431211987d81903dd89777a184 [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 Hung35f96152022-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 Hung2aa15102022-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 Hung2aa15102022-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 Hung2aa15102022-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 Hung2aa15102022-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 Hungf8ab0932022-06-13 19:49:43 -0700133 std::string_view tag, TimerCallback&& func,
134 Duration timeoutDuration, Duration secondChanceDuration);
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800135
136 /**
Andy Hunga2a1ac32022-03-18 16:12:11 -0700137 * Tracks a task that shows up on toString() until cancelled.
138 *
139 * \param tag string associated with the task.
140 * \returns a handle that can be used for cancellation.
141 */
Andy Hung35f96152022-07-15 15:18:59 -0700142 Handle trackTask(std::string_view tag);
Andy Hunga2a1ac32022-03-18 16:12:11 -0700143
144 /**
145 * Cancels a task previously scheduled with scheduleTask()
146 * or trackTask().
147 *
148 * \returns true if cancelled. If the task has already executed
149 * or if the handle doesn't exist, this is a no-op
150 * and returns false.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800151 */
Andy Hung5c6d68a2022-03-09 21:54:59 -0800152 bool cancelTask(Handle handle);
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800153
Andy Hunga2a1ac32022-03-18 16:12:11 -0700154 std::string toString(size_t retiredCount = SIZE_MAX) const;
155
156 /**
157 * Returns a string representation of the TimerThread queue.
158 *
159 * The queue is dumped in order of scheduling (not deadline).
160 */
161 std::string pendingToString() const;
162
163 /**
164 * Returns a string representation of the last retired tasks.
165 *
166 * These tasks from trackTask() or scheduleTask() are
167 * cancelled.
168 *
169 * These are ordered when the task was retired.
170 *
171 * \param n is maximum number of tasks to dump.
172 */
173 std::string retiredToString(size_t n = SIZE_MAX) const;
174
175
176 /**
177 * Returns a string representation of the last timeout tasks.
178 *
179 * These tasks from scheduleTask() which have timed-out.
180 *
181 * These are ordered when the task had timed-out.
182 *
183 * \param n is maximum number of tasks to dump.
184 */
185 std::string timeoutToString(size_t n = SIZE_MAX) const;
186
187 /**
188 * Dumps a container with SmartPointer<Request> to a string.
189 *
190 * "{ Request1 } { Request2} ...{ RequestN }"
191 */
192 template <typename T>
193 static std::string requestsToString(const T& containerRequests) {
194 std::string s;
195 // append seems to be faster than stringstream.
196 // https://stackoverflow.com/questions/18892281/most-optimized-way-of-concatenation-in-strings
197 for (const auto& request : containerRequests) {
198 s.append("{ ").append(request->toString()).append(" } ");
199 }
200 // If not empty, there's an extra space at the end, so we trim it off.
201 if (!s.empty()) s.pop_back();
202 return s;
203 }
204
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800205 private:
Andy Hunga2a1ac32022-03-18 16:12:11 -0700206 // To minimize movement of data, we pass around shared_ptrs to Requests.
207 // These are allocated and deallocated outside of the lock.
Andy Hungf8ab0932022-06-13 19:49:43 -0700208 // TODO(b/243839867) consider options to merge Request with the
209 // TimeCheck::TimeCheckHandler struct.
Andy Hunga2a1ac32022-03-18 16:12:11 -0700210 struct Request {
Andy Hung2aa15102022-06-13 19:49:43 -0700211 Request(std::chrono::system_clock::time_point _scheduled,
212 std::chrono::system_clock::time_point _deadline,
Andy Hungf8ab0932022-06-13 19:49:43 -0700213 Duration _secondChanceDuration,
Andy Hung35f96152022-07-15 15:18:59 -0700214 pid_t _tid,
215 std::string_view _tag)
216 : scheduled(_scheduled)
217 , deadline(_deadline)
Andy Hungf8ab0932022-06-13 19:49:43 -0700218 , secondChanceDuration(_secondChanceDuration)
Andy Hung35f96152022-07-15 15:18:59 -0700219 , tid(_tid)
220 , tag(_tag)
221 {}
222
Andy Hunga2a1ac32022-03-18 16:12:11 -0700223 const std::chrono::system_clock::time_point scheduled;
Andy Hungf8ab0932022-06-13 19:49:43 -0700224 const std::chrono::system_clock::time_point deadline; // deadline := scheduled
225 // + timeoutDuration
226 // + secondChanceDuration
Andy Hunga2a1ac32022-03-18 16:12:11 -0700227 // if deadline == scheduled, no
228 // timeout, task not executed.
Andy Hungf8ab0932022-06-13 19:49:43 -0700229 Duration secondChanceDuration;
Andy Hunga2a1ac32022-03-18 16:12:11 -0700230 const pid_t tid;
Andy Hung35f96152022-07-15 15:18:59 -0700231 const FixedString62 tag;
Andy Hunga2a1ac32022-03-18 16:12:11 -0700232 std::string toString() const;
233 };
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800234
Andy Hunga2a1ac32022-03-18 16:12:11 -0700235 // Deque of requests, in order of add().
236 // This class is thread-safe.
237 class RequestQueue {
238 public:
239 explicit RequestQueue(size_t maxSize)
240 : mRequestQueueMax(maxSize) {}
241
242 void add(std::shared_ptr<const Request>);
243
244 // return up to the last "n" requests retired.
245 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests,
246 size_t n = SIZE_MAX) const;
247
248 private:
249 const size_t mRequestQueueMax;
250 mutable std::mutex mRQMutex;
251 std::deque<std::pair<std::chrono::system_clock::time_point,
252 std::shared_ptr<const Request>>>
253 mRequestQueue GUARDED_BY(mRQMutex);
254 };
255
Andy Hung2aa15102022-06-13 19:49:43 -0700256 // A storage map of tasks without timeouts. There is no TimerCallback
Andy Hunga2a1ac32022-03-18 16:12:11 -0700257 // required, it just tracks the tasks with the tag, scheduled time and the tid.
258 // These tasks show up on a pendingToString() until manually cancelled.
259 class NoTimeoutMap {
Andy Hunga2a1ac32022-03-18 16:12:11 -0700260 mutable std::mutex mNTMutex;
261 std::map<Handle, std::shared_ptr<const Request>> mMap GUARDED_BY(mNTMutex);
Andy Hung2aa15102022-06-13 19:49:43 -0700262 Handle getUniqueHandle_l() REQUIRES(mNTMutex) {
263 return getUniqueHandleForHandleType_l<
264 HANDLE_TYPES, enum_as_value(HANDLE_TYPE::NO_TIMEOUT)>(
265 mMap, Duration{} /* timeout */);
266 }
Andy Hunga2a1ac32022-03-18 16:12:11 -0700267
268 public:
269 bool isValidHandle(Handle handle) const; // lock free
270 Handle add(std::shared_ptr<const Request> request);
271 std::shared_ptr<const Request> remove(Handle handle);
272 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
273 };
274
275 // Monitor thread.
276 // This thread manages shared pointers to Requests and a function to
277 // call on timeout.
278 // This class is thread-safe.
279 class MonitorThread {
Andy Hungf8ab0932022-06-13 19:49:43 -0700280 std::atomic<size_t> mSecondChanceCount{};
Andy Hunga2a1ac32022-03-18 16:12:11 -0700281 mutable std::mutex mMutex;
Andy Hung2aa15102022-06-13 19:49:43 -0700282 mutable std::condition_variable mCond GUARDED_BY(mMutex);
Andy Hunga2a1ac32022-03-18 16:12:11 -0700283
284 // Ordered map of requests based on time of deadline.
285 //
Andy Hung2aa15102022-06-13 19:49:43 -0700286 std::map<Handle, std::pair<std::shared_ptr<const Request>, TimerCallback>>
Andy Hunga2a1ac32022-03-18 16:12:11 -0700287 mMonitorRequests GUARDED_BY(mMutex);
288
Andy Hungf8ab0932022-06-13 19:49:43 -0700289 // Due to monotonic/steady clock inaccuracies during suspend,
290 // we allow an additional second chance waiting time to prevent
291 // false removal.
292
293 // This mSecondChanceRequests queue is almost always empty.
294 // Using a pair with the original handle allows lookup and keeps
295 // the Key unique.
296 std::map<std::pair<Handle /* new */, Handle /* original */>,
297 std::pair<std::shared_ptr<const Request>, TimerCallback>>
298 mSecondChanceRequests GUARDED_BY(mMutex);
299
Andy Hunga2a1ac32022-03-18 16:12:11 -0700300 RequestQueue& mTimeoutQueue; // locked internally, added to when request times out.
301
302 // Worker thread variables
303 bool mShouldExit GUARDED_BY(mMutex) = false;
304
305 // To avoid race with initialization,
306 // mThread should be initialized last as the thread is launched immediately.
307 std::thread mThread;
308
309 void threadFunc();
Andy Hung2aa15102022-06-13 19:49:43 -0700310 Handle getUniqueHandle_l(Duration timeout) REQUIRES(mMutex) {
311 return getUniqueHandleForHandleType_l<
312 HANDLE_TYPES, enum_as_value(HANDLE_TYPE::TIMEOUT)>(
313 mMonitorRequests, timeout);
314 }
Andy Hunga2a1ac32022-03-18 16:12:11 -0700315
316 public:
317 MonitorThread(RequestQueue &timeoutQueue);
318 ~MonitorThread();
319
Andy Hung2aa15102022-06-13 19:49:43 -0700320 Handle add(std::shared_ptr<const Request> request, TimerCallback&& func,
321 Duration timeout);
Andy Hunga2a1ac32022-03-18 16:12:11 -0700322 std::shared_ptr<const Request> remove(Handle handle);
323 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
Andy Hungf8ab0932022-06-13 19:49:43 -0700324 size_t getSecondChanceCount() const {
325 return mSecondChanceCount.load(std::memory_order_relaxed);
326 }
Andy Hunga2a1ac32022-03-18 16:12:11 -0700327 };
328
Andy Hungf45f34c2022-03-25 13:09:03 -0700329 // Analysis contains info deduced by analysisTimeout().
330 //
331 // Summary is the result string from checking timeoutRequests to see if
332 // any might be caused by blocked calls in pendingRequests.
333 //
334 // Summary string is empty if there is no automatic actionable info.
335 //
336 // timeoutTid is the tid selected from timeoutRequests (if any).
337 //
338 // HALBlockedTid is the tid that is blocked from pendingRequests believed
339 // to cause the timeout.
340 // HALBlockedTid may be INVALID_PID if no suspected tid is found,
341 // and if HALBlockedTid is valid, it will not be the same as timeoutTid.
342 //
343 static constexpr pid_t INVALID_PID = -1;
344 struct Analysis {
345 std::string summary;
346 pid_t timeoutTid = INVALID_PID;
347 pid_t HALBlockedTid = INVALID_PID;
348 };
349
350 // A HAL method is where the substring "Hidl" is in the class name.
351 // The tag should look like: ... Hidl ... :: ...
352 static bool isRequestFromHal(const std::shared_ptr<const Request>& request);
353
354 // Returns analysis from the requests.
355 static Analysis analyzeTimeout(
356 const std::vector<std::shared_ptr<const Request>>& timeoutRequests,
357 const std::vector<std::shared_ptr<const Request>>& pendingRequests);
358
Andy Hunga2a1ac32022-03-18 16:12:11 -0700359 std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
360
Andy Hunga2a1ac32022-03-18 16:12:11 -0700361 static constexpr size_t kRetiredQueueMax = 16;
362 RequestQueue mRetiredQueue{kRetiredQueueMax}; // locked internally
363
364 static constexpr size_t kTimeoutQueueMax = 16;
365 RequestQueue mTimeoutQueue{kTimeoutQueueMax}; // locked internally
366
367 NoTimeoutMap mNoTimeoutMap; // locked internally
368
369 MonitorThread mMonitorThread{mTimeoutQueue}; // This should be initialized last because
370 // the thread is launched immediately.
371 // Locked internally.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800372};
373
Andy Hunga2a1ac32022-03-18 16:12:11 -0700374} // namespace android::mediautils