• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
19 #include <atomic>
20 #include <condition_variable>
21 #include <deque>
22 #include <functional>
23 #include <map>
24 #include <mutex>
25 #include <string>
26 #include <thread>
27 
28 #include <android-base/thread_annotations.h>
29 
30 #include <mediautils/FixedString.h>
31 
32 namespace android::mediautils {
33 
34 /**
35  * A thread for deferred execution of tasks, with cancellation.
36  */
37 class TimerThread {
38   public:
39     // A Handle is a time_point that serves as a unique key to access a queued
40     // request to the TimerThread.
41     using Handle = std::chrono::steady_clock::time_point;
42 
43     // 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 
47     static inline constexpr Handle INVALID_HANDLE =
48             std::chrono::steady_clock::time_point::min();
49 
50     // 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>
enum_as_value(T x)77     static constexpr auto enum_as_value(T x) {
78         return static_cast<std::underlying_type_t<T>>(x);
79     }
80 
isNoTimeoutHandle(Handle handle)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 
isTimeoutHandle(Handle handle)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>
getUniqueHandleForHandleType_l(C container,T timeout)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 
119     /**
120      * 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.
125      * \param timeoutDuration timeout duration which is converted to milliseconds with at
126      *                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.
131      */
132     Handle scheduleTask(
133             std::string_view tag, TimerCallback&& func,
134             Duration timeoutDuration, Duration secondChanceDuration);
135 
136     /**
137      * 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      */
142     Handle trackTask(std::string_view tag);
143 
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.
151      */
152     bool cancelTask(Handle handle);
153 
154     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>
requestsToString(const T & containerRequests)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 
205   private:
206     // To minimize movement of data, we pass around shared_ptrs to Requests.
207     // These are allocated and deallocated outside of the lock.
208     // TODO(b/243839867) consider options to merge Request with the
209     // TimeCheck::TimeCheckHandler struct.
210     struct Request {
RequestRequest211         Request(std::chrono::system_clock::time_point _scheduled,
212                 std::chrono::system_clock::time_point _deadline,
213                 Duration _secondChanceDuration,
214                 pid_t _tid,
215                 std::string_view _tag)
216             : scheduled(_scheduled)
217             , deadline(_deadline)
218             , secondChanceDuration(_secondChanceDuration)
219             , tid(_tid)
220             , tag(_tag)
221             {}
222 
223         const std::chrono::system_clock::time_point scheduled;
224         const std::chrono::system_clock::time_point deadline; // deadline := scheduled
225                                                               // + timeoutDuration
226                                                               // + secondChanceDuration
227                                                               // if deadline == scheduled, no
228                                                               // timeout, task not executed.
229         Duration secondChanceDuration;
230         const pid_t tid;
231         const FixedString62 tag;
232         std::string toString() const;
233     };
234 
235     // Deque of requests, in order of add().
236     // This class is thread-safe.
237     class RequestQueue {
238       public:
RequestQueue(size_t maxSize)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 
256     // A storage map of tasks without timeouts.  There is no TimerCallback
257     // 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 {
260         mutable std::mutex mNTMutex;
261         std::map<Handle, std::shared_ptr<const Request>> mMap GUARDED_BY(mNTMutex);
getUniqueHandle_l()262         Handle getUniqueHandle_l() REQUIRES(mNTMutex) {
263             return getUniqueHandleForHandleType_l<
264                     HANDLE_TYPES, enum_as_value(HANDLE_TYPE::NO_TIMEOUT)>(
265                 mMap, Duration{} /* timeout */);
266         }
267 
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 {
280         std::atomic<size_t> mSecondChanceCount{};
281         mutable std::mutex mMutex;
282         mutable std::condition_variable mCond GUARDED_BY(mMutex);
283 
284         // Ordered map of requests based on time of deadline.
285         //
286         std::map<Handle, std::pair<std::shared_ptr<const Request>, TimerCallback>>
287                 mMonitorRequests GUARDED_BY(mMutex);
288 
289         // 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 
300         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();
getUniqueHandle_l(Duration timeout)310         Handle getUniqueHandle_l(Duration timeout) REQUIRES(mMutex) {
311             return getUniqueHandleForHandleType_l<
312                     HANDLE_TYPES, enum_as_value(HANDLE_TYPE::TIMEOUT)>(
313                 mMonitorRequests, timeout);
314         }
315 
316       public:
317         MonitorThread(RequestQueue &timeoutQueue);
318         ~MonitorThread();
319 
320         Handle add(std::shared_ptr<const Request> request, TimerCallback&& func,
321                 Duration timeout);
322         std::shared_ptr<const Request> remove(Handle handle);
323         void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
getSecondChanceCount()324         size_t getSecondChanceCount() const {
325             return mSecondChanceCount.load(std::memory_order_relaxed);
326         }
327     };
328 
329     // 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 
359     std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
360 
361     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.
372 };
373 
374 }  // namespace android::mediautils
375