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 "RecurrentTimer.h"
18
19 #include <android-base/thread_annotations.h>
20 #include <gtest/gtest.h>
21 #include <condition_variable>
22
23 #include <chrono>
24 #include <memory>
25 #include <mutex>
26
27 namespace android {
28 namespace hardware {
29 namespace automotive {
30 namespace vehicle {
31
32 using ::android::base::ScopedLockAssertion;
33
34 class RecurrentTimerTest : public testing::Test {
35 public:
getCallback(size_t token)36 std::shared_ptr<RecurrentTimer::Callback> getCallback(size_t token) {
37 return std::make_shared<RecurrentTimer::Callback>([this, token] {
38 std::scoped_lock<std::mutex> lockGuard(mLock);
39
40 mCallbacks.push_back(token);
41 mCond.notify_all();
42 });
43 }
44
waitForCalledCallbacks(size_t count,size_t timeoutInMs)45 bool waitForCalledCallbacks(size_t count, size_t timeoutInMs) {
46 std::unique_lock<std::mutex> uniqueLock(mLock);
47 return mCond.wait_for(uniqueLock, std::chrono::milliseconds(timeoutInMs), [this, count] {
48 ScopedLockAssertion lockAssertion(mLock);
49 return mCallbacks.size() >= count;
50 });
51 }
52
getCalledCallbacks()53 std::vector<size_t> getCalledCallbacks() {
54 std::scoped_lock<std::mutex> lockGuard(mLock);
55 return mCallbacks;
56 }
57
clearCalledCallbacks()58 void clearCalledCallbacks() {
59 std::scoped_lock<std::mutex> lockGuard(mLock);
60 mCallbacks.clear();
61 }
62
countCallbackInfoById(RecurrentTimer * timer)63 size_t countCallbackInfoById(RecurrentTimer* timer) {
64 std::scoped_lock<std::mutex> lockGuard(timer->mLock);
65 return timer->mCallbackInfoById.size();
66 }
67
countIdByCallback(RecurrentTimer * timer)68 size_t countIdByCallback(RecurrentTimer* timer) {
69 std::scoped_lock<std::mutex> lockGuard(timer->mLock);
70 return timer->mIdByCallback.size();
71 }
72
73 private:
74 std::condition_variable mCond;
75 std::mutex mLock;
76 std::vector<size_t> mCallbacks GUARDED_BY(mLock);
77 };
78
TEST_F(RecurrentTimerTest,testRegisterCallback)79 TEST_F(RecurrentTimerTest, testRegisterCallback) {
80 RecurrentTimer timer;
81 // 0.1s
82 int64_t interval = 100000000;
83
84 auto action = getCallback(0);
85 timer.registerTimerCallback(interval, action);
86
87 // Should only takes 1s, use 5s as timeout to be safe.
88 ASSERT_TRUE(waitForCalledCallbacks(/* count= */ 10u, /* timeoutInMs= */ 5000))
89 << "Not enough callbacks called before timeout";
90
91 timer.unregisterTimerCallback(action);
92 }
93
TEST_F(RecurrentTimerTest,testRegisterUnregisterRegister)94 TEST_F(RecurrentTimerTest, testRegisterUnregisterRegister) {
95 RecurrentTimer timer;
96 // 0.1s
97 int64_t interval = 100000000;
98
99 auto action = getCallback(0);
100 timer.registerTimerCallback(interval, action);
101
102 std::this_thread::sleep_for(std::chrono::milliseconds(200));
103
104 timer.unregisterTimerCallback(action);
105
106 std::this_thread::sleep_for(std::chrono::milliseconds(200));
107
108 clearCalledCallbacks();
109
110 timer.registerTimerCallback(interval, action);
111
112 // Should only takes 1s, use 5s as timeout to be safe.
113 ASSERT_TRUE(waitForCalledCallbacks(/* count= */ 10u, /* timeoutInMs= */ 5000))
114 << "Not enough callbacks called before timeout";
115
116 timer.unregisterTimerCallback(action);
117
118 ASSERT_EQ(countCallbackInfoById(&timer), 0u);
119 ASSERT_EQ(countIdByCallback(&timer), 0u);
120 }
121
TEST_F(RecurrentTimerTest,testDestroyTimerWithCallback)122 TEST_F(RecurrentTimerTest, testDestroyTimerWithCallback) {
123 std::unique_ptr<RecurrentTimer> timer = std::make_unique<RecurrentTimer>();
124 // 0.1s
125 int64_t interval = 100000000;
126
127 auto action = getCallback(0);
128 timer->registerTimerCallback(interval, action);
129
130 std::this_thread::sleep_for(std::chrono::milliseconds(200));
131
132 timer.reset();
133
134 clearCalledCallbacks();
135
136 std::this_thread::sleep_for(std::chrono::milliseconds(200));
137
138 // Should be 0, but in rare cases there might be 1 events in the queue while the timer is
139 // being destroyed.
140 ASSERT_LE(getCalledCallbacks().size(), 1u);
141 }
142
TEST_F(RecurrentTimerTest,testRegisterMultipleCallbacks)143 TEST_F(RecurrentTimerTest, testRegisterMultipleCallbacks) {
144 RecurrentTimer timer;
145 // 0.1s
146 int64_t interval1 = 100000000;
147 auto action1 = getCallback(1);
148 timer.registerTimerCallback(interval1, action1);
149 // 0.05s
150 int64_t interval2 = 50000000;
151 auto action2 = getCallback(2);
152 timer.registerTimerCallback(interval2, action2);
153 // 0.03s
154 int64_t interval3 = 30000000;
155 auto action3 = getCallback(3);
156 timer.registerTimerCallback(interval3, action3);
157
158 // In 1s, we should generate 10 + 20 + 33 = 63 events.
159 // Here we are waiting for more events to make sure we receive enough events for each actions.
160 // Use 5s as timeout to be safe.
161 ASSERT_TRUE(waitForCalledCallbacks(/* count= */ 70u, /* timeoutInMs= */ 5000))
162 << "Not enough callbacks called before timeout";
163
164 timer.unregisterTimerCallback(action1);
165 timer.unregisterTimerCallback(action2);
166 timer.unregisterTimerCallback(action3);
167
168 size_t action1Count = 0;
169 size_t action2Count = 0;
170 size_t action3Count = 0;
171 for (size_t token : getCalledCallbacks()) {
172 if (token == 1) {
173 action1Count++;
174 }
175 if (token == 2) {
176 action2Count++;
177 }
178 if (token == 3) {
179 action3Count++;
180 }
181 }
182
183 ASSERT_GE(action1Count, static_cast<size_t>(10));
184 ASSERT_GE(action2Count, static_cast<size_t>(20));
185 ASSERT_GE(action3Count, static_cast<size_t>(33));
186 }
187
TEST_F(RecurrentTimerTest,testRegisterSameCallbackMultipleTimes)188 TEST_F(RecurrentTimerTest, testRegisterSameCallbackMultipleTimes) {
189 RecurrentTimer timer;
190 // 0.2s
191 int64_t interval1 = 200'000'000;
192 // 0.1s
193 int64_t interval2 = 100'000'000;
194
195 auto action = getCallback(0);
196 for (int i = 0; i < 10; i++) {
197 timer.registerTimerCallback(interval1, action);
198 timer.registerTimerCallback(interval2, action);
199 }
200
201 clearCalledCallbacks();
202
203 // Should only takes 1s, use 5s as timeout to be safe.
204 ASSERT_TRUE(waitForCalledCallbacks(/* count= */ 10u, /* timeoutInMs= */ 5000))
205 << "Not enough callbacks called before timeout";
206
207 timer.unregisterTimerCallback(action);
208
209 ASSERT_EQ(countCallbackInfoById(&timer), 0u);
210 ASSERT_EQ(countIdByCallback(&timer), 0u);
211 }
212
TEST_F(RecurrentTimerTest,testRegisterCallbackMultipleTimesNoDeadLock)213 TEST_F(RecurrentTimerTest, testRegisterCallbackMultipleTimesNoDeadLock) {
214 // We want to avoid the following situation:
215 // Caller holds a lock while calling registerTimerCallback, registerTimerCallback will try
216 // to obtain an internal lock inside timer.
217 // Meanwhile an recurrent action happens with timer holding an internal lock. The action
218 // tries to obtain the lock currently hold by the caller.
219 // The solution is that while calling recurrent actions, timer must not hold the internal lock.
220
221 std::unique_ptr<RecurrentTimer> timer = std::make_unique<RecurrentTimer>();
222 std::mutex lock;
223 for (size_t i = 0; i < 1000; i++) {
224 std::scoped_lock<std::mutex> lockGuard(lock);
225 auto action = std::make_shared<RecurrentTimer::Callback>([&lock] {
226 // While calling this function, the timer must not hold lock in order not to dead
227 // lock.
228 std::scoped_lock<std::mutex> lockGuard(lock);
229 });
230 // 10ms
231 int64_t interval = 10'000'000;
232 timer->registerTimerCallback(interval, action);
233 // Sleep for a little while to let the recurrent actions begin.
234 std::this_thread::sleep_for(std::chrono::milliseconds(1));
235 }
236 // Make sure we stop the timer before we destroy lock.
237 timer.reset();
238 }
239
240 } // namespace vehicle
241 } // namespace automotive
242 } // namespace hardware
243 } // namespace android
244