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