1 /*
2 * Copyright (C) 2024 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 "chre/util/system/transaction_manager.h"
18
19 #include <algorithm>
20 #include <map>
21
22 #include "chre/core/event_loop_common.h"
23 #include "chre/core/timer_pool.h"
24 #include "chre/platform/linux/system_time.h"
25
26 #include "gmock/gmock.h"
27 #include "gtest/gtest.h"
28
29 using chre::platform_linux::SystemTimeOverride;
30 using testing::_;
31 using testing::Return;
32
33 namespace chre {
34 namespace {
35
36 constexpr size_t kMaxTransactions = 32;
37 constexpr Nanoseconds kTimeout = Milliseconds(10);
38 constexpr uint16_t kMaxAttempts = 3;
39
40 } // anonymous namespace
41
42 class MockTimerPool {
43 public:
44 MOCK_METHOD(TimerHandle, setSystemTimer,
45 (Nanoseconds, SystemEventCallbackFunction, SystemCallbackType,
46 void *));
47 MOCK_METHOD(bool, cancelSystemTimer, (TimerHandle));
48 };
49
50 class FakeTimerPool {
51 public:
setSystemTimer(Nanoseconds duration,SystemEventCallbackFunction * callback,SystemCallbackType,void * data)52 TimerHandle setSystemTimer(Nanoseconds duration,
53 SystemEventCallbackFunction *callback,
54 SystemCallbackType /*callbackType*/, void *data) {
55 Timer timer = {
56 .expiry = SystemTime::getMonotonicTime() + duration,
57 .callback = callback,
58 .data = data,
59 };
60 TimerHandle handle = mNextHandle++;
61 mTimers[handle] = timer;
62 return handle;
63 }
cancelSystemTimer(TimerHandle handle)64 bool cancelSystemTimer(TimerHandle handle) {
65 return mTimers.erase(handle) == 1;
66 }
67
68 //! Advance the time to the next expiring timer and invoke its callback
69 //! @return false if no timers exist
invokeNextTimer(SystemTimeOverride & time,Nanoseconds additionalDelay=Nanoseconds (0))70 bool invokeNextTimer(SystemTimeOverride &time,
71 Nanoseconds additionalDelay = Nanoseconds(0)) {
72 auto it = std::min_element(mTimers.begin(), mTimers.end(),
73 [](const auto &a, const auto &b) {
74 return a.second.expiry < b.second.expiry;
75 });
76 if (it == mTimers.end()) {
77 return false;
78 }
79 Timer timer = it->second;
80 mTimers.erase(it);
81 time.update(timer.expiry + additionalDelay);
82 timer.callback(/*type=*/0, timer.data, /*extraData=*/nullptr);
83 return true;
84 }
85
86 struct Timer {
87 Nanoseconds expiry;
88 SystemEventCallbackFunction *callback;
89 void *data;
90 };
91
92 TimerHandle mNextHandle = 1;
93 std::map<TimerHandle, Timer> mTimers;
94 };
95
96 class MockTransactionManagerCallback : public TransactionManagerCallback {
97 public:
98 MOCK_METHOD(void, onTransactionAttempt, (uint32_t, uint16_t), (override));
99 MOCK_METHOD(void, onTransactionFailure, (uint32_t, uint16_t), (override));
100 };
101
102 class FakeTransactionManagerCallback : public TransactionManagerCallback {
103 public:
onTransactionAttempt(uint32_t transactionId,uint16_t)104 void onTransactionAttempt(uint32_t transactionId,
105 uint16_t /*groupId*/) override {
106 mTries.push_back(transactionId);
107 }
onTransactionFailure(uint32_t transactionId,uint16_t)108 void onTransactionFailure(uint32_t transactionId,
109 uint16_t /*groupId*/) override {
110 mFailures.push_back(transactionId);
111 }
112
113 std::vector<uint32_t> mTries;
114 std::vector<uint32_t> mFailures;
115 };
116
117 using TxnMgr = TransactionManager<kMaxTransactions, MockTimerPool>;
118 using TxnMgrF = TransactionManager<kMaxTransactions, FakeTimerPool>;
119
120 class TransactionManagerTest : public testing::Test {
121 public:
122 protected:
defaultTxnMgr()123 TxnMgr defaultTxnMgr() {
124 return TxnMgr(mFakeCb, mTimerPool, kTimeout, kMaxAttempts);
125 }
126
defaultTxnMgrF()127 TxnMgrF defaultTxnMgrF() {
128 return TxnMgrF(mFakeCb, mFakeTimerPool, kTimeout, kMaxAttempts);
129 }
130
131 static constexpr uint32_t kTimerId = 1;
132
133 MockTimerPool mTimerPool;
134 FakeTimerPool mFakeTimerPool;
135 FakeTransactionManagerCallback mFakeCb;
136 MockTransactionManagerCallback mMockCb;
137 SystemTimeOverride mTime = SystemTimeOverride(0);
138 };
139
TEST_F(TransactionManagerTest,StartSingleTransaction)140 TEST_F(TransactionManagerTest, StartSingleTransaction) {
141 TxnMgr tm = defaultTxnMgr();
142
143 EXPECT_CALL(mTimerPool, setSystemTimer(kTimeout, _, _, _))
144 .Times(1)
145 .WillOnce(Return(kTimerId));
146
147 uint32_t id;
148 EXPECT_TRUE(tm.add(/*groupId=*/0, &id));
149
150 ASSERT_EQ(mFakeCb.mTries.size(), 1);
151 EXPECT_EQ(mFakeCb.mTries[0], id);
152 EXPECT_EQ(mFakeCb.mFailures.size(), 0);
153 }
154
TEST_F(TransactionManagerTest,RemoveSingleTransaction)155 TEST_F(TransactionManagerTest, RemoveSingleTransaction) {
156 TxnMgr tm = defaultTxnMgr();
157
158 EXPECT_CALL(mTimerPool, setSystemTimer(_, _, _, _))
159 .Times(1)
160 .WillOnce(Return(kTimerId));
161
162 uint32_t id;
163 ASSERT_TRUE(tm.add(/*groupId=*/0, &id));
164
165 EXPECT_CALL(mTimerPool, cancelSystemTimer(kTimerId))
166 .Times(1)
167 .WillOnce(Return(true));
168
169 EXPECT_TRUE(tm.remove(id));
170 EXPECT_EQ(mFakeCb.mTries.size(), 1);
171 EXPECT_EQ(mFakeCb.mFailures.size(), 0);
172 }
173
TEST_F(TransactionManagerTest,SingleTransactionSuccessOnRetry)174 TEST_F(TransactionManagerTest, SingleTransactionSuccessOnRetry) {
175 TxnMgrF tm = defaultTxnMgrF();
176
177 uint32_t id;
178 ASSERT_TRUE(tm.add(0, &id));
179 EXPECT_TRUE(mFakeTimerPool.invokeNextTimer(mTime));
180 EXPECT_EQ(mFakeCb.mTries.size(), 2);
181
182 EXPECT_TRUE(tm.remove(id));
183 ASSERT_EQ(mFakeCb.mTries.size(), 2);
184 EXPECT_EQ(mFakeCb.mTries[0], id);
185 EXPECT_EQ(mFakeCb.mTries[1], id);
186 EXPECT_EQ(mFakeCb.mFailures.size(), 0);
187 EXPECT_FALSE(mFakeTimerPool.invokeNextTimer(mTime));
188 }
189
TEST_F(TransactionManagerTest,SingleTransactionTimeout)190 TEST_F(TransactionManagerTest, SingleTransactionTimeout) {
191 TxnMgrF tm = defaultTxnMgrF();
192
193 uint32_t id;
194 ASSERT_TRUE(tm.add(0, &id));
195 size_t count = 0;
196 while (mFakeTimerPool.invokeNextTimer(mTime) && count++ < kMaxAttempts * 2);
197 EXPECT_EQ(count, kMaxAttempts);
198 EXPECT_EQ(std::count(mFakeCb.mTries.begin(), mFakeCb.mTries.end(), id),
199 kMaxAttempts);
200 ASSERT_EQ(mFakeCb.mFailures.size(), 1);
201 EXPECT_EQ(mFakeCb.mFailures[0], id);
202
203 // The transaction should actually be gone
204 EXPECT_FALSE(tm.remove(id));
205 EXPECT_FALSE(mFakeTimerPool.invokeNextTimer(mTime));
206 }
207
TEST_F(TransactionManagerTest,TwoTransactionsDifferentGroups)208 TEST_F(TransactionManagerTest, TwoTransactionsDifferentGroups) {
209 TxnMgrF tm = defaultTxnMgrF();
210
211 uint32_t id1;
212 uint32_t id2;
213 EXPECT_TRUE(tm.add(/*groupId=*/0, &id1));
214 EXPECT_TRUE(tm.add(/*groupId=*/1, &id2));
215
216 // Both should start
217 ASSERT_EQ(mFakeCb.mTries.size(), 2);
218 EXPECT_EQ(mFakeCb.mTries[0], id1);
219 EXPECT_EQ(mFakeCb.mTries[1], id2);
220 EXPECT_EQ(mFakeCb.mFailures.size(), 0);
221 }
222
TEST_F(TransactionManagerTest,TwoTransactionsSameGroup)223 TEST_F(TransactionManagerTest, TwoTransactionsSameGroup) {
224 TxnMgrF tm = defaultTxnMgrF();
225
226 uint32_t id1;
227 uint32_t id2;
228 EXPECT_TRUE(tm.add(/*groupId=*/0, &id1));
229 EXPECT_TRUE(tm.add(/*groupId=*/0, &id2));
230
231 // Only the first should start
232 ASSERT_EQ(mFakeCb.mTries.size(), 1);
233 EXPECT_EQ(mFakeCb.mTries[0], id1);
234
235 // Second starts after the first finishes
236 EXPECT_TRUE(tm.remove(id1));
237 ASSERT_EQ(mFakeCb.mTries.size(), 2);
238 EXPECT_EQ(mFakeCb.mTries[1], id2);
239
240 // Second completes with no funny business
241 EXPECT_TRUE(tm.remove(id2));
242 EXPECT_EQ(mFakeCb.mTries.size(), 2);
243 EXPECT_EQ(mFakeCb.mFailures.size(), 0);
244 EXPECT_FALSE(mFakeTimerPool.invokeNextTimer(mTime));
245 }
246
TEST_F(TransactionManagerTest,TwoTransactionsSameGroupTimeout)247 TEST_F(TransactionManagerTest, TwoTransactionsSameGroupTimeout) {
248 TxnMgrF tm = defaultTxnMgrF();
249
250 uint32_t id1;
251 uint32_t id2;
252 EXPECT_TRUE(tm.add(/*groupId=*/0, &id1));
253 EXPECT_TRUE(tm.add(/*groupId=*/0, &id2));
254
255 // Time out the first transaction, which should kick off the second
256 for (size_t i = 0; i < kMaxAttempts; i++) {
257 EXPECT_TRUE(mFakeTimerPool.invokeNextTimer(mTime));
258 }
259 ASSERT_EQ(mFakeCb.mTries.size(), kMaxAttempts + 1);
260 EXPECT_EQ(std::count(mFakeCb.mTries.begin(), mFakeCb.mTries.end(), id1),
261 kMaxAttempts);
262 EXPECT_EQ(mFakeCb.mTries.back(), id2);
263
264 // Retry + time out behavior for second works the same as the first
265 for (size_t i = 0; i < kMaxAttempts; i++) {
266 EXPECT_TRUE(mFakeTimerPool.invokeNextTimer(mTime));
267 }
268 ASSERT_EQ(mFakeCb.mTries.size(), kMaxAttempts * 2);
269 EXPECT_EQ(std::count(mFakeCb.mTries.begin(), mFakeCb.mTries.end(), id2),
270 kMaxAttempts);
271 ASSERT_EQ(mFakeCb.mFailures.size(), 2);
272 EXPECT_EQ(mFakeCb.mFailures[0], id1);
273 EXPECT_EQ(mFakeCb.mFailures[1], id2);
274 EXPECT_FALSE(mFakeTimerPool.invokeNextTimer(mTime));
275 }
276
TEST_F(TransactionManagerTest,TwoTransactionsSameGroupRemoveReverseOrder)277 TEST_F(TransactionManagerTest, TwoTransactionsSameGroupRemoveReverseOrder) {
278 TxnMgrF tm = defaultTxnMgrF();
279
280 uint32_t id1;
281 uint32_t id2;
282 EXPECT_TRUE(tm.add(/*groupId=*/0, &id1));
283 EXPECT_TRUE(tm.add(/*groupId=*/0, &id2));
284
285 // Only the first should start
286 ASSERT_EQ(mFakeCb.mTries.size(), 1);
287 EXPECT_EQ(mFakeCb.mTries[0], id1);
288
289 // Remove second one first
290 EXPECT_TRUE(tm.remove(id2));
291
292 // Finish the first one
293 EXPECT_TRUE(tm.remove(id1));
294 ASSERT_EQ(mFakeCb.mTries.size(), 1);
295 EXPECT_EQ(mFakeCb.mTries[0], id1);
296 EXPECT_EQ(mFakeCb.mFailures.size(), 0);
297 EXPECT_FALSE(mFakeTimerPool.invokeNextTimer(mTime));
298 }
299
TEST_F(TransactionManagerTest,MultipleTimeouts)300 TEST_F(TransactionManagerTest, MultipleTimeouts) {
301 TxnMgrF tm = defaultTxnMgrF();
302
303 // Timeout both in a single callback
304 uint32_t ids[2];
305 EXPECT_TRUE(tm.add(/*groupId=*/0, &ids[0]));
306 mTime.update(kTimeout.toRawNanoseconds() / 2);
307 EXPECT_TRUE(tm.add(/*groupId=*/1, &ids[1]));
308 EXPECT_TRUE(mFakeTimerPool.invokeNextTimer(mTime, kTimeout));
309 EXPECT_EQ(mFakeCb.mTries.size(), 4);
310
311 // Since both retries were dispatched at the same time, they should time out
312 // again together
313 EXPECT_TRUE(mFakeTimerPool.invokeNextTimer(mTime, kTimeout));
314 EXPECT_EQ(mFakeCb.mTries.size(), 6);
315
316 // If changing the max # of attempts, modify the below code too so it triggers
317 // failure
318 static_assert(kMaxAttempts == 3);
319 EXPECT_TRUE(mFakeTimerPool.invokeNextTimer(mTime, kTimeout));
320 EXPECT_EQ(mFakeCb.mTries.size(), 6);
321 for (size_t i = 0; i < mFakeCb.mTries.size(); i++) {
322 EXPECT_EQ(mFakeCb.mTries[i], ids[i % 2]);
323 }
324 ASSERT_EQ(mFakeCb.mFailures.size(), 2);
325 EXPECT_EQ(mFakeCb.mFailures[0], ids[0]);
326 EXPECT_EQ(mFakeCb.mFailures[1], ids[1]);
327 EXPECT_FALSE(mFakeTimerPool.invokeNextTimer(mTime));
328 }
329
TEST_F(TransactionManagerTest,CallbackUsesCorrectGroupId)330 TEST_F(TransactionManagerTest, CallbackUsesCorrectGroupId) {
331 TxnMgrF tm(mMockCb, mFakeTimerPool, kTimeout, /*maxAttempts=*/1);
332
333 EXPECT_CALL(mMockCb, onTransactionAttempt(_, 1)).Times(1);
334 EXPECT_CALL(mMockCb, onTransactionAttempt(_, 2)).Times(1);
335 EXPECT_CALL(mMockCb, onTransactionAttempt(_, 3)).Times(1);
336
337 uint32_t id;
338 tm.add(1, &id);
339 tm.add(2, &id);
340 tm.add(3, &id);
341
342 EXPECT_CALL(mMockCb, onTransactionFailure(_, 1)).Times(1);
343 EXPECT_CALL(mMockCb, onTransactionFailure(_, 2)).Times(1);
344 EXPECT_CALL(mMockCb, onTransactionFailure(_, 3)).Times(1);
345
346 mFakeTimerPool.invokeNextTimer(mTime);
347 mFakeTimerPool.invokeNextTimer(mTime);
348 mFakeTimerPool.invokeNextTimer(mTime);
349 }
350
351 } // namespace chre
352