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 #ifndef CHRE_UTIL_SYSTEM_TRANSACTION_MANAGER_IMPL_H_
18 #define CHRE_UTIL_SYSTEM_TRANSACTION_MANAGER_IMPL_H_
19
20 // IWYU pragma: private
21 #include "chre/util/system/transaction_manager.h"
22
23 #include "chre/core/event_loop_common.h"
24 #include "chre/platform/system_time.h"
25 #include "chre/util/hash.h"
26
27 namespace chre {
28
29 template <size_t kMaxTransactions, class TimerPoolType>
~TransactionManager()30 TransactionManager<kMaxTransactions, TimerPoolType>::~TransactionManager() {
31 if (mTimerHandle != CHRE_TIMER_INVALID) {
32 LOGI("At least one pending transaction at destruction");
33 mTimerPool.cancelSystemTimer(mTimerHandle);
34 }
35 }
36
37 template <size_t kMaxTransactions, class TimerPoolType>
add(uint16_t groupId,uint32_t * id)38 bool TransactionManager<kMaxTransactions, TimerPoolType>::add(uint16_t groupId,
39 uint32_t *id) {
40 CHRE_ASSERT(id != nullptr);
41 CHRE_ASSERT(!mInCallback);
42
43 if (mTransactions.full()) {
44 LOGE("Can't add new transaction: storage is full");
45 return false;
46 }
47
48 if (!mNextTransactionId.has_value()) {
49 mNextTransactionId = generatePseudoRandomId();
50 }
51 *id = (mNextTransactionId.value())++;
52 mTransactions.emplace(*id, groupId);
53
54 maybeStartLastTransaction();
55 if (mTransactions.size() == 1) {
56 setTimerAbsolute(mTransactions.back().timeout);
57 }
58 return true;
59 }
60
61 template <size_t kMaxTransactions, class TimerPoolType>
remove(uint32_t transactionId)62 bool TransactionManager<kMaxTransactions, TimerPoolType>::remove(
63 uint32_t transactionId) {
64 CHRE_ASSERT(!mInCallback);
65 for (size_t i = 0; i < mTransactions.size(); ++i) {
66 Transaction &transaction = mTransactions[i];
67 if (transaction.id == transactionId) {
68 uint16_t groupId = transaction.groupId;
69 bool transactionWasStarted = transaction.attemptCount > 0;
70 mTransactions.remove(i);
71
72 if (transactionWasStarted) {
73 startNextTransactionInGroup(groupId);
74 updateTimer();
75 }
76 return true;
77 }
78 }
79 return false;
80 }
81
82 template <size_t kMaxTransactions, class TimerPoolType>
83 uint32_t
generatePseudoRandomId()84 TransactionManager<kMaxTransactions, TimerPoolType>::generatePseudoRandomId() {
85 uint64_t data =
86 SystemTime::getMonotonicTime().toRawNanoseconds() +
87 static_cast<uint64_t>(SystemTime::getEstimatedHostTimeOffset());
88 uint32_t hash =
89 fnv1a32Hash(reinterpret_cast<const uint8_t *>(&data), sizeof(data));
90
91 // We mix the top 2 bits back into the middle of the hash to provide a value
92 // that leaves a gap of at least ~1 billion sequence numbers before
93 // overflowing a signed int32 (as used on the Java side).
94 constexpr uint32_t kMask = 0xC0000000;
95 constexpr uint32_t kShiftAmount = 17;
96 uint32_t extraBits = hash & kMask;
97 hash ^= extraBits >> kShiftAmount;
98 return hash & ~kMask;
99 }
100
101 template <size_t kMaxTransactions, class TimerPoolType>
102 void TransactionManager<kMaxTransactions,
maybeStartLastTransaction()103 TimerPoolType>::maybeStartLastTransaction() {
104 Transaction &lastTransaction = mTransactions.back();
105
106 for (const Transaction &transaction : mTransactions) {
107 if (transaction.groupId == lastTransaction.groupId &&
108 transaction.id != lastTransaction.id) {
109 // Have at least one pending request for this group, so this transaction
110 // will only be started via removeTransaction()
111 return;
112 }
113 }
114
115 startTransaction(lastTransaction);
116 }
117
118 template <size_t kMaxTransactions, class TimerPoolType>
119 void TransactionManager<kMaxTransactions, TimerPoolType>::
startNextTransactionInGroup(uint16_t groupId)120 startNextTransactionInGroup(uint16_t groupId) {
121 for (Transaction &transaction : mTransactions) {
122 if (transaction.groupId == groupId) {
123 startTransaction(transaction);
124 return;
125 }
126 }
127 }
128
129 template <size_t kMaxTransactions, class TimerPoolType>
startTransaction(Transaction & transaction)130 void TransactionManager<kMaxTransactions, TimerPoolType>::startTransaction(
131 Transaction &transaction) {
132 CHRE_ASSERT(transaction.attemptCount == 0);
133 transaction.attemptCount = 1;
134 transaction.timeout = SystemTime::getMonotonicTime() + kTimeout;
135 {
136 ScopedFlag f(mInCallback);
137 mCb.onTransactionAttempt(transaction.id, transaction.groupId);
138 }
139 }
140
141 template <size_t kMaxTransactions, class TimerPoolType>
updateTimer()142 void TransactionManager<kMaxTransactions, TimerPoolType>::updateTimer() {
143 mTimerPool.cancelSystemTimer(mTimerHandle);
144 if (mTransactions.empty()) {
145 mTimerHandle = CHRE_TIMER_INVALID;
146 } else {
147 Nanoseconds nextTimeout(UINT64_MAX);
148 for (const Transaction &transaction : mTransactions) {
149 if (transaction.timeout < nextTimeout) {
150 nextTimeout = transaction.timeout;
151 }
152 }
153 // If we hit this assert, we only have transactions that haven't been
154 // started yet
155 CHRE_ASSERT(nextTimeout.toRawNanoseconds() != UINT64_MAX);
156 setTimerAbsolute(nextTimeout);
157 }
158 }
159
160 template <size_t kMaxTransactions, class TimerPoolType>
setTimer(Nanoseconds duration)161 void TransactionManager<kMaxTransactions, TimerPoolType>::setTimer(
162 Nanoseconds duration) {
163 mTimerHandle = mTimerPool.setSystemTimer(
164 duration, onTimerExpired, SystemCallbackType::TransactionManagerTimeout,
165 /*data=*/this);
166 }
167
168 template <size_t kMaxTransactions, class TimerPoolType>
setTimerAbsolute(Nanoseconds expiry)169 void TransactionManager<kMaxTransactions, TimerPoolType>::setTimerAbsolute(
170 Nanoseconds expiry) {
171 constexpr Nanoseconds kMinDelay(100);
172 Nanoseconds now = SystemTime::getMonotonicTime();
173 Nanoseconds delay = (expiry > now) ? expiry - now : kMinDelay;
174 setTimer(delay);
175 }
176
177 template <size_t kMaxTransactions, class TimerPoolType>
handleTimerExpiry()178 void TransactionManager<kMaxTransactions, TimerPoolType>::handleTimerExpiry() {
179 mTimerHandle = CHRE_TIMER_INVALID;
180 if (mTransactions.empty()) {
181 LOGW("Got timer callback with no pending transactions");
182 return;
183 }
184
185 // - If a transaction has reached its timeout, try again
186 // - If a transaction has timed out for the final time, fail it
187 // - If another transaction in the same group is pending, start it
188 // - Keep track of the transaction with the shortest timeout, use that to
189 // update the timer
190 Nanoseconds now = SystemTime::getMonotonicTime();
191 Nanoseconds nextTimeout(UINT64_MAX);
192 for (size_t i = 0; i < mTransactions.size(); /* ++i at end of scope */) {
193 Transaction &transaction = mTransactions[i];
194 if (transaction.timeout <= now) {
195 if (++transaction.attemptCount > kMaxAttempts) {
196 Transaction transactionCopy = transaction;
197 mTransactions.remove(i); // Invalidates transaction reference
198 handleTransactionFailure(transactionCopy);
199 // Since mTransactions is FIFO, any pending transactions in this group
200 // will appear after this one, so we don't need to restart the loop
201 continue;
202 } else {
203 transaction.timeout = now + kTimeout;
204 {
205 ScopedFlag f(mInCallback);
206 mCb.onTransactionAttempt(transaction.id, transaction.groupId);
207 }
208 }
209 }
210 if (transaction.timeout < nextTimeout) {
211 nextTimeout = transaction.timeout;
212 }
213 ++i;
214 }
215
216 if (!mTransactions.empty()) {
217 setTimerAbsolute(nextTimeout);
218 }
219 }
220
221 template <size_t kMaxTransactions, class TimerPoolType>
222 void TransactionManager<kMaxTransactions, TimerPoolType>::
handleTransactionFailure(Transaction & transaction)223 handleTransactionFailure(Transaction &transaction) {
224 {
225 ScopedFlag f(mInCallback);
226 mCb.onTransactionFailure(transaction.id, transaction.groupId);
227 }
228 startNextTransactionInGroup(transaction.groupId);
229 }
230
231 } // namespace chre
232
233 #endif // CHRE_UTIL_SYSTEM_TRANSACTION_MANAGER_IMPL_H_
234