blob: ffee60262f1be211171128f085c5d4d010d9ec13 [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
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800128 private:
Andy Hunga2a1ac32022-03-18 16:12:11 -0700129 // To minimize movement of data, we pass around shared_ptrs to Requests.
130 // These are allocated and deallocated outside of the lock.
131 struct Request {
132 const std::chrono::system_clock::time_point scheduled;
133 const std::chrono::system_clock::time_point deadline; // deadline := scheduled + timeout
134 // if deadline == scheduled, no
135 // timeout, task not executed.
136 const pid_t tid;
137 const std::string tag;
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800138
Andy Hunga2a1ac32022-03-18 16:12:11 -0700139 std::string toString() const;
140 };
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800141
Andy Hunga2a1ac32022-03-18 16:12:11 -0700142 // Deque of requests, in order of add().
143 // This class is thread-safe.
144 class RequestQueue {
145 public:
146 explicit RequestQueue(size_t maxSize)
147 : mRequestQueueMax(maxSize) {}
148
149 void add(std::shared_ptr<const Request>);
150
151 // return up to the last "n" requests retired.
152 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests,
153 size_t n = SIZE_MAX) const;
154
155 private:
156 const size_t mRequestQueueMax;
157 mutable std::mutex mRQMutex;
158 std::deque<std::pair<std::chrono::system_clock::time_point,
159 std::shared_ptr<const Request>>>
160 mRequestQueue GUARDED_BY(mRQMutex);
161 };
162
163 // A storage map of tasks without timeouts. There is no std::function<void()>
164 // required, it just tracks the tasks with the tag, scheduled time and the tid.
165 // These tasks show up on a pendingToString() until manually cancelled.
166 class NoTimeoutMap {
167 // This a counter of the requests that have no timeout (timeout == 0).
168 std::atomic<size_t> mNoTimeoutRequests{};
169
170 mutable std::mutex mNTMutex;
171 std::map<Handle, std::shared_ptr<const Request>> mMap GUARDED_BY(mNTMutex);
172
173 public:
174 bool isValidHandle(Handle handle) const; // lock free
175 Handle add(std::shared_ptr<const Request> request);
176 std::shared_ptr<const Request> remove(Handle handle);
177 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
178 };
179
180 // Monitor thread.
181 // This thread manages shared pointers to Requests and a function to
182 // call on timeout.
183 // This class is thread-safe.
184 class MonitorThread {
185 mutable std::mutex mMutex;
186 mutable std::condition_variable mCond;
187
188 // Ordered map of requests based on time of deadline.
189 //
190 std::map<Handle, std::pair<std::shared_ptr<const Request>, std::function<void()>>>
191 mMonitorRequests GUARDED_BY(mMutex);
192
193 RequestQueue& mTimeoutQueue; // locked internally, added to when request times out.
194
195 // Worker thread variables
196 bool mShouldExit GUARDED_BY(mMutex) = false;
197
198 // To avoid race with initialization,
199 // mThread should be initialized last as the thread is launched immediately.
200 std::thread mThread;
201
202 void threadFunc();
203 Handle getUniqueHandle_l(std::chrono::milliseconds timeout) REQUIRES(mMutex);
204
205 public:
206 MonitorThread(RequestQueue &timeoutQueue);
207 ~MonitorThread();
208
209 Handle add(std::shared_ptr<const Request> request, std::function<void()>&& func,
210 std::chrono::milliseconds timeout);
211 std::shared_ptr<const Request> remove(Handle handle);
212 void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
213 };
214
Andy Hungf45f34c2022-03-25 13:09:03 -0700215 // Analysis contains info deduced by analysisTimeout().
216 //
217 // Summary is the result string from checking timeoutRequests to see if
218 // any might be caused by blocked calls in pendingRequests.
219 //
220 // Summary string is empty if there is no automatic actionable info.
221 //
222 // timeoutTid is the tid selected from timeoutRequests (if any).
223 //
224 // HALBlockedTid is the tid that is blocked from pendingRequests believed
225 // to cause the timeout.
226 // HALBlockedTid may be INVALID_PID if no suspected tid is found,
227 // and if HALBlockedTid is valid, it will not be the same as timeoutTid.
228 //
229 static constexpr pid_t INVALID_PID = -1;
230 struct Analysis {
231 std::string summary;
232 pid_t timeoutTid = INVALID_PID;
233 pid_t HALBlockedTid = INVALID_PID;
234 };
235
236 // A HAL method is where the substring "Hidl" is in the class name.
237 // The tag should look like: ... Hidl ... :: ...
238 static bool isRequestFromHal(const std::shared_ptr<const Request>& request);
239
240 // Returns analysis from the requests.
241 static Analysis analyzeTimeout(
242 const std::vector<std::shared_ptr<const Request>>& timeoutRequests,
243 const std::vector<std::shared_ptr<const Request>>& pendingRequests);
244
Andy Hunga2a1ac32022-03-18 16:12:11 -0700245 std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
246
247 // A no-timeout request is represented by a handles at the end of steady_clock time,
248 // counting down by the number of no timeout requests previously requested.
249 // We manage them on the NoTimeoutMap, but conceptually they could be scheduled
250 // on the MonitorThread because those time handles won't expire in
251 // the lifetime of the device.
252 static inline Handle getIndexedHandle(size_t index) {
253 return std::chrono::time_point<std::chrono::steady_clock>::max() -
254 std::chrono::time_point<std::chrono::steady_clock>::duration(index);
255 }
256
257 static constexpr size_t kRetiredQueueMax = 16;
258 RequestQueue mRetiredQueue{kRetiredQueueMax}; // locked internally
259
260 static constexpr size_t kTimeoutQueueMax = 16;
261 RequestQueue mTimeoutQueue{kTimeoutQueueMax}; // locked internally
262
263 NoTimeoutMap mNoTimeoutMap; // locked internally
264
265 MonitorThread mMonitorThread{mTimeoutQueue}; // This should be initialized last because
266 // the thread is launched immediately.
267 // Locked internally.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800268};
269
Andy Hunga2a1ac32022-03-18 16:12:11 -0700270} // namespace android::mediautils