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 #include <chrono>
18 #include <thread>
19 #include <gtest/gtest.h>
20 #include <mediautils/TimerThread.h>
21
22 using namespace std::chrono_literals;
23 using namespace android::mediautils;
24
25 namespace {
26
27 constexpr auto kJitter = 10ms;
28
29 // Each task written by *ToString() will start with a left brace.
30 constexpr char REQUEST_START = '{';
31
countChars(std::string_view s,char c)32 inline size_t countChars(std::string_view s, char c) {
33 return std::count(s.begin(), s.end(), c);
34 }
35
36
37 // Split msec time between timeout and second chance time
38 // This tests expiration times weighted between timeout and the second chance time.
39 #define DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(msec, frac) \
40 std::chrono::milliseconds(int((msec) * (frac)) + 1), \
41 std::chrono::milliseconds(int((msec) * (1.f - (frac))))
42
43 // The TimerThreadTest is parameterized on a fraction between 0.f and 1.f which
44 // is how the total timeout time is split between the first timeout and the second chance time.
45 //
46 class TimerThreadTest : public ::testing::TestWithParam<float> {
47 protected:
48
testBasic()49 static void testBasic() {
50 const auto frac = GetParam();
51
52 std::atomic<bool> taskRan = false;
53 TimerThread thread;
54 TimerThread::Handle handle =
55 thread.scheduleTask("Basic", [&taskRan](TimerThread::Handle) {
56 taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
57 ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
58 std::this_thread::sleep_for(100ms - kJitter);
59 ASSERT_FALSE(taskRan);
60 std::this_thread::sleep_for(2 * kJitter);
61 ASSERT_TRUE(taskRan); // timed-out called.
62 ASSERT_EQ(1ul, countChars(thread.timeoutToString(), REQUEST_START));
63 // nothing cancelled
64 ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START));
65 }
66
testCancel()67 static void testCancel() {
68 const auto frac = GetParam();
69
70 std::atomic<bool> taskRan = false;
71 TimerThread thread;
72 TimerThread::Handle handle =
73 thread.scheduleTask("Cancel", [&taskRan](TimerThread::Handle) {
74 taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
75 ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
76 std::this_thread::sleep_for(100ms - kJitter);
77 ASSERT_FALSE(taskRan);
78 ASSERT_TRUE(thread.cancelTask(handle));
79 std::this_thread::sleep_for(2 * kJitter);
80 ASSERT_FALSE(taskRan); // timed-out did not call.
81 ASSERT_EQ(0ul, countChars(thread.timeoutToString(), REQUEST_START));
82 // task cancelled.
83 ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START));
84 }
85
testCancelAfterRun()86 static void testCancelAfterRun() {
87 const auto frac = GetParam();
88
89 std::atomic<bool> taskRan = false;
90 TimerThread thread;
91 TimerThread::Handle handle =
92 thread.scheduleTask("CancelAfterRun",
93 [&taskRan](TimerThread::Handle) {
94 taskRan = true; },
95 DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
96 ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
97 std::this_thread::sleep_for(100ms + kJitter);
98 ASSERT_TRUE(taskRan); // timed-out called.
99 ASSERT_FALSE(thread.cancelTask(handle));
100 ASSERT_EQ(1ul, countChars(thread.timeoutToString(), REQUEST_START));
101 // nothing actually cancelled
102 ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START));
103 }
104
testMultipleTasks()105 static void testMultipleTasks() {
106 const auto frac = GetParam();
107
108 std::array<std::atomic<bool>, 6> taskRan{};
109 TimerThread thread;
110
111 auto startTime = std::chrono::steady_clock::now();
112
113 thread.scheduleTask("0", [&taskRan](TimerThread::Handle) {
114 taskRan[0] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(300, frac));
115 thread.scheduleTask("1", [&taskRan](TimerThread::Handle) {
116 taskRan[1] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
117 thread.scheduleTask("2", [&taskRan](TimerThread::Handle) {
118 taskRan[2] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
119 thread.scheduleTask("3", [&taskRan](TimerThread::Handle) {
120 taskRan[3] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(400, frac));
121 auto handle4 = thread.scheduleTask("4", [&taskRan](TimerThread::Handle) {
122 taskRan[4] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
123 thread.scheduleTask("5", [&taskRan](TimerThread::Handle) {
124 taskRan[5] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
125
126 // 6 tasks pending
127 ASSERT_EQ(6ul, countChars(thread.pendingToString(), REQUEST_START));
128 // 0 tasks completed
129 ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START));
130
131 // None of the tasks are expected to have finished at the start.
132 std::array<std::atomic<bool>, 6> expected{};
133
134 // Task 1 should trigger around 100ms.
135 std::this_thread::sleep_until(startTime + 100ms - kJitter);
136
137 ASSERT_EQ(expected, taskRan);
138
139
140 std::this_thread::sleep_until(startTime + 100ms + kJitter);
141
142 expected[1] = true;
143 ASSERT_EQ(expected, taskRan);
144
145 // Cancel task 4 before it gets a chance to run.
146 thread.cancelTask(handle4);
147
148 // Tasks 2 and 5 should trigger around 200ms.
149 std::this_thread::sleep_until(startTime + 200ms - kJitter);
150
151 ASSERT_EQ(expected, taskRan);
152
153
154 std::this_thread::sleep_until(startTime + 200ms + kJitter);
155
156 expected[2] = true;
157 expected[5] = true;
158 ASSERT_EQ(expected, taskRan);
159
160 // Task 0 should trigger around 300ms.
161 std::this_thread::sleep_until(startTime + 300ms - kJitter);
162
163 ASSERT_EQ(expected, taskRan);
164
165 std::this_thread::sleep_until(startTime + 300ms + kJitter);
166
167 expected[0] = true;
168 ASSERT_EQ(expected, taskRan);
169
170 // 1 task pending
171 ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START));
172 // 4 tasks called on timeout, and 1 cancelled
173 ASSERT_EQ(4ul, countChars(thread.timeoutToString(), REQUEST_START));
174 ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START));
175
176 // Task 3 should trigger around 400ms.
177 std::this_thread::sleep_until(startTime + 400ms - kJitter);
178
179 ASSERT_EQ(expected, taskRan);
180
181 // 4 tasks called on timeout and 1 cancelled
182 ASSERT_EQ(4ul, countChars(thread.timeoutToString(), REQUEST_START));
183 ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START));
184
185 std::this_thread::sleep_until(startTime + 400ms + kJitter);
186
187 expected[3] = true;
188 ASSERT_EQ(expected, taskRan);
189
190 // 0 tasks pending
191 ASSERT_EQ(0ul, countChars(thread.pendingToString(), REQUEST_START));
192 // 5 tasks called on timeout and 1 cancelled
193 ASSERT_EQ(5ul, countChars(thread.timeoutToString(), REQUEST_START));
194 ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START));
195 }
196
197 }; // class TimerThreadTest
198
TEST_P(TimerThreadTest,Basic)199 TEST_P(TimerThreadTest, Basic) {
200 testBasic();
201 }
202
TEST_P(TimerThreadTest,Cancel)203 TEST_P(TimerThreadTest, Cancel) {
204 testCancel();
205 }
206
TEST_P(TimerThreadTest,CancelAfterRun)207 TEST_P(TimerThreadTest, CancelAfterRun) {
208 testCancelAfterRun();
209 }
210
TEST_P(TimerThreadTest,MultipleTasks)211 TEST_P(TimerThreadTest, MultipleTasks) {
212 testMultipleTasks();
213 }
214
215 INSTANTIATE_TEST_CASE_P(
216 TimerThread,
217 TimerThreadTest,
218 ::testing::Values(0.f, 0.5f, 1.f)
219 );
220
TEST(TimerThread,TrackedTasks)221 TEST(TimerThread, TrackedTasks) {
222 TimerThread thread;
223
224 auto handle0 = thread.trackTask("0");
225 auto handle1 = thread.trackTask("1");
226 auto handle2 = thread.trackTask("2");
227
228 ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle0));
229 ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle1));
230 ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle2));
231
232 // 3 tasks pending
233 ASSERT_EQ(3ul, countChars(thread.pendingToString(), REQUEST_START));
234 // 0 tasks retired
235 ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START));
236
237 ASSERT_TRUE(thread.cancelTask(handle0));
238 ASSERT_TRUE(thread.cancelTask(handle1));
239
240 // 1 task pending
241 ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START));
242 // 2 tasks retired
243 ASSERT_EQ(2ul, countChars(thread.retiredToString(), REQUEST_START));
244
245 // handle1 is stale, cancel returns false.
246 ASSERT_FALSE(thread.cancelTask(handle1));
247
248 // 1 task pending
249 ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START));
250 // 2 tasks retired
251 ASSERT_EQ(2ul, countChars(thread.retiredToString(), REQUEST_START));
252
253 // Add another tracked task.
254 auto handle3 = thread.trackTask("3");
255 ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle3));
256
257 // 2 tasks pending
258 ASSERT_EQ(2ul, countChars(thread.pendingToString(), REQUEST_START));
259 // 2 tasks retired
260 ASSERT_EQ(2ul, countChars(thread.retiredToString(), REQUEST_START));
261
262 ASSERT_TRUE(thread.cancelTask(handle2));
263
264 // 1 tasks pending
265 ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START));
266 // 3 tasks retired
267 ASSERT_EQ(3ul, countChars(thread.retiredToString(), REQUEST_START));
268
269 ASSERT_TRUE(thread.cancelTask(handle3));
270
271 // 0 tasks pending
272 ASSERT_EQ(0ul, countChars(thread.pendingToString(), REQUEST_START));
273 // 4 tasks retired
274 ASSERT_EQ(4ul, countChars(thread.retiredToString(), REQUEST_START));
275 }
276
277 } // namespace
278