blob: 2dfe49995ea3ef3d9af82c50cdb693f95f114429 [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
215 std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
216
217 // A no-timeout request is represented by a handles at the end of steady_clock time,
218 // counting down by the number of no timeout requests previously requested.
219 // We manage them on the NoTimeoutMap, but conceptually they could be scheduled
220 // on the MonitorThread because those time handles won't expire in
221 // the lifetime of the device.
222 static inline Handle getIndexedHandle(size_t index) {
223 return std::chrono::time_point<std::chrono::steady_clock>::max() -
224 std::chrono::time_point<std::chrono::steady_clock>::duration(index);
225 }
226
227 static constexpr size_t kRetiredQueueMax = 16;
228 RequestQueue mRetiredQueue{kRetiredQueueMax}; // locked internally
229
230 static constexpr size_t kTimeoutQueueMax = 16;
231 RequestQueue mTimeoutQueue{kTimeoutQueueMax}; // locked internally
232
233 NoTimeoutMap mNoTimeoutMap; // locked internally
234
235 MonitorThread mMonitorThread{mTimeoutQueue}; // This should be initialized last because
236 // the thread is launched immediately.
237 // Locked internally.
Ytai Ben-Tsvi1ea62c92021-11-10 14:38:27 -0800238};
239
Andy Hunga2a1ac32022-03-18 16:12:11 -0700240} // namespace android::mediautils