• 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 #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 handle __unused) {
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);
62     ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
63 }
64 
testCancel()65 static void testCancel() {
66     const auto frac = GetParam();
67 
68     std::atomic<bool> taskRan = false;
69     TimerThread thread;
70     TimerThread::Handle handle =
71             thread.scheduleTask("Cancel", [&taskRan](TimerThread::Handle handle __unused) {
72                     taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
73     ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
74     std::this_thread::sleep_for(100ms - kJitter);
75     ASSERT_FALSE(taskRan);
76     ASSERT_TRUE(thread.cancelTask(handle));
77     std::this_thread::sleep_for(2 * kJitter);
78     ASSERT_FALSE(taskRan);
79     ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
80 }
81 
testCancelAfterRun()82 static void testCancelAfterRun() {
83     const auto frac = GetParam();
84 
85     std::atomic<bool> taskRan = false;
86     TimerThread thread;
87     TimerThread::Handle handle =
88             thread.scheduleTask("CancelAfterRun",
89                     [&taskRan](TimerThread::Handle handle __unused) {
90                             taskRan = true; },
91                             DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
92     ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
93     std::this_thread::sleep_for(100ms + kJitter);
94     ASSERT_TRUE(taskRan);
95     ASSERT_FALSE(thread.cancelTask(handle));
96     ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
97 }
98 
testMultipleTasks()99 static void testMultipleTasks() {
100     const auto frac = GetParam();
101 
102     std::array<std::atomic<bool>, 6> taskRan{};
103     TimerThread thread;
104 
105     auto startTime = std::chrono::steady_clock::now();
106 
107     thread.scheduleTask("0", [&taskRan](TimerThread::Handle handle __unused) {
108             taskRan[0] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(300, frac));
109     thread.scheduleTask("1", [&taskRan](TimerThread::Handle handle __unused) {
110             taskRan[1] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
111     thread.scheduleTask("2", [&taskRan](TimerThread::Handle handle __unused) {
112             taskRan[2] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
113     thread.scheduleTask("3", [&taskRan](TimerThread::Handle handle __unused) {
114             taskRan[3] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(400, frac));
115     auto handle4 = thread.scheduleTask("4", [&taskRan](TimerThread::Handle handle __unused) {
116             taskRan[4] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
117     thread.scheduleTask("5", [&taskRan](TimerThread::Handle handle __unused) {
118             taskRan[5] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
119 
120     // 6 tasks pending
121     ASSERT_EQ(6, countChars(thread.pendingToString(), REQUEST_START));
122     // 0 tasks completed
123     ASSERT_EQ(0, countChars(thread.retiredToString(), REQUEST_START));
124 
125     // None of the tasks are expected to have finished at the start.
126     std::array<std::atomic<bool>, 6> expected{};
127 
128     // Task 1 should trigger around 100ms.
129     std::this_thread::sleep_until(startTime + 100ms - kJitter);
130 
131     ASSERT_EQ(expected, taskRan);
132 
133 
134     std::this_thread::sleep_until(startTime + 100ms + kJitter);
135 
136     expected[1] = true;
137     ASSERT_EQ(expected, taskRan);
138 
139     // Cancel task 4 before it gets a chance to run.
140     thread.cancelTask(handle4);
141 
142     // Tasks 2 and 5 should trigger around 200ms.
143     std::this_thread::sleep_until(startTime + 200ms - kJitter);
144 
145     ASSERT_EQ(expected, taskRan);
146 
147 
148     std::this_thread::sleep_until(startTime + 200ms + kJitter);
149 
150     expected[2] = true;
151     expected[5] = true;
152     ASSERT_EQ(expected, taskRan);
153 
154     // Task 0 should trigger around 300ms.
155     std::this_thread::sleep_until(startTime + 300ms - kJitter);
156 
157     ASSERT_EQ(expected, taskRan);
158 
159     std::this_thread::sleep_until(startTime + 300ms + kJitter);
160 
161     expected[0] = true;
162     ASSERT_EQ(expected, taskRan);
163 
164     // 1 task pending
165     ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
166     // 4 tasks ran and 1 cancelled
167     ASSERT_EQ(4 + 1, countChars(thread.retiredToString(), REQUEST_START));
168 
169     // Task 3 should trigger around 400ms.
170     std::this_thread::sleep_until(startTime + 400ms - kJitter);
171 
172     ASSERT_EQ(expected, taskRan);
173 
174     // 4 tasks ran and 1 cancelled
175     ASSERT_EQ(4 + 1, countChars(thread.retiredToString(), REQUEST_START));
176 
177     std::this_thread::sleep_until(startTime + 400ms + kJitter);
178 
179     expected[3] = true;
180     ASSERT_EQ(expected, taskRan);
181 
182     // 0 tasks pending
183     ASSERT_EQ(0, countChars(thread.pendingToString(), REQUEST_START));
184     // 5 tasks ran and 1 cancelled
185     ASSERT_EQ(5 + 1, countChars(thread.retiredToString(), REQUEST_START));
186 }
187 
188 }; // class TimerThreadTest
189 
TEST_P(TimerThreadTest,Basic)190 TEST_P(TimerThreadTest, Basic) {
191     testBasic();
192 }
193 
TEST_P(TimerThreadTest,Cancel)194 TEST_P(TimerThreadTest, Cancel) {
195     testCancel();
196 }
197 
TEST_P(TimerThreadTest,CancelAfterRun)198 TEST_P(TimerThreadTest, CancelAfterRun) {
199     testCancelAfterRun();
200 }
201 
TEST_P(TimerThreadTest,MultipleTasks)202 TEST_P(TimerThreadTest, MultipleTasks) {
203     testMultipleTasks();
204 }
205 
206 INSTANTIATE_TEST_CASE_P(
207         TimerThread,
208         TimerThreadTest,
209         ::testing::Values(0.f, 0.5f, 1.f)
210         );
211 
TEST(TimerThread,TrackedTasks)212 TEST(TimerThread, TrackedTasks) {
213     TimerThread thread;
214 
215     auto handle0 = thread.trackTask("0");
216     auto handle1 = thread.trackTask("1");
217     auto handle2 = thread.trackTask("2");
218 
219     ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle0));
220     ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle1));
221     ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle2));
222 
223     // 3 tasks pending
224     ASSERT_EQ(3, countChars(thread.pendingToString(), REQUEST_START));
225     // 0 tasks retired
226     ASSERT_EQ(0, countChars(thread.retiredToString(), REQUEST_START));
227 
228     ASSERT_TRUE(thread.cancelTask(handle0));
229     ASSERT_TRUE(thread.cancelTask(handle1));
230 
231     // 1 task pending
232     ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
233     // 2 tasks retired
234     ASSERT_EQ(2, countChars(thread.retiredToString(), REQUEST_START));
235 
236     // handle1 is stale, cancel returns false.
237     ASSERT_FALSE(thread.cancelTask(handle1));
238 
239     // 1 task pending
240     ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
241     // 2 tasks retired
242     ASSERT_EQ(2, countChars(thread.retiredToString(), REQUEST_START));
243 
244     // Add another tracked task.
245     auto handle3 = thread.trackTask("3");
246     ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle3));
247 
248     // 2 tasks pending
249     ASSERT_EQ(2, countChars(thread.pendingToString(), REQUEST_START));
250     // 2 tasks retired
251     ASSERT_EQ(2, countChars(thread.retiredToString(), REQUEST_START));
252 
253     ASSERT_TRUE(thread.cancelTask(handle2));
254 
255     // 1 tasks pending
256     ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
257     // 3 tasks retired
258     ASSERT_EQ(3, countChars(thread.retiredToString(), REQUEST_START));
259 
260     ASSERT_TRUE(thread.cancelTask(handle3));
261 
262     // 0 tasks pending
263     ASSERT_EQ(0, countChars(thread.pendingToString(), REQUEST_START));
264     // 4 tasks retired
265     ASSERT_EQ(4, countChars(thread.retiredToString(), REQUEST_START));
266 }
267 
268 }  // namespace
269