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_TRANSACTION_MANAGER_IMPL_H_
18 #define CHRE_UTIL_TRANSACTION_MANAGER_IMPL_H_
19
20 #include <algorithm>
21 #include <inttypes.h>
22
23 #include "chre/platform/system_time.h"
24 #include "chre/util/hash.h"
25 #include "chre/util/lock_guard.h"
26 #include "chre/util/transaction_manager.h"
27 #include "chre_api/chre.h"
28
29 namespace chre {
30
31 using ::chre::Nanoseconds;
32 using ::chre::Seconds;
33
34 template <typename TransactionData, size_t kMaxTransactions>
completeTransaction(uint32_t transactionId,uint8_t errorCode)35 bool TransactionManager<TransactionData, kMaxTransactions>::completeTransaction(
36 uint32_t transactionId, uint8_t errorCode) {
37 bool success = false;
38
39 {
40 LockGuard<Mutex> lock(mMutex);
41 for (size_t i = 0; i < mTransactions.size(); ++i) {
42 Transaction &transaction = mTransactions[i];
43 if (transaction.id == transactionId) {
44 if (errorCode == CHRE_ERROR_TRANSIENT) {
45 transaction.nextRetryTime = Nanoseconds(0);
46 } else {
47 transaction.errorCode = errorCode;
48 }
49 success = true;
50 break;
51 }
52 }
53 }
54
55 if (success) {
56 deferProcessTransactions();
57 } else {
58 LOGE("Unable to complete transaction with ID: %" PRIu32, transactionId);
59 }
60 return success;
61 }
62
63 template <typename TransactionData, size_t kMaxTransactions>
flushTransactions(FlushCallback callback,void * data)64 size_t TransactionManager<TransactionData, kMaxTransactions>::flushTransactions(
65 FlushCallback callback, void *data) {
66 if (callback == nullptr) {
67 return 0;
68 }
69
70 deferProcessTransactions();
71
72 LockGuard<Mutex> lock(mMutex);
73 size_t numFlushed = 0;
74 for (size_t i = 0; i < mTransactions.size();) {
75 if (callback(mTransactions[i].data, data)) {
76 mTransactions.remove(i);
77 ++numFlushed;
78 } else {
79 ++i;
80 }
81 }
82 return numFlushed;
83 }
84
85 template <typename TransactionData, size_t kMaxTransactions>
startTransaction(const TransactionData & data,uint16_t cookie,uint32_t * id)86 bool TransactionManager<TransactionData, kMaxTransactions>::startTransaction(
87 const TransactionData &data, uint16_t cookie, uint32_t *id) {
88 CHRE_ASSERT(id != nullptr);
89
90 {
91 LockGuard<Mutex> lock(mMutex);
92 if (mTransactions.full()) {
93 LOGE("The transaction queue is full");
94 return false;
95 }
96
97 if (!mNextTransactionId.has_value()) {
98 mNextTransactionId = generatePseudoRandomId();
99 }
100 uint32_t transactionId = (mNextTransactionId.value())++;
101 *id = transactionId;
102
103 Transaction transaction{
104 .id = transactionId,
105 .data = data,
106 .nextRetryTime = Nanoseconds(0),
107 .timeoutTime = Nanoseconds(0),
108 .cookie = cookie,
109 .numCompletedStartCalls = 0,
110 .errorCode = Optional<uint8_t>(),
111 };
112
113 mTransactions.push(transaction);
114 }
115
116 deferProcessTransactions();
117 return true;
118 }
119
120 template <typename TransactionData, size_t kMaxTransactions>
121 void TransactionManager<TransactionData,
deferProcessTransactions()122 kMaxTransactions>::deferProcessTransactions() {
123 bool success = kDeferCallback(
124 [](uint16_t /* type */, void *data, void * /* extraData */) {
125 auto transactionManagerPtr = static_cast<TransactionManager *>(data);
126 if (transactionManagerPtr == nullptr) {
127 LOGE("Could not get transaction manager to process transactions");
128 return;
129 }
130
131 transactionManagerPtr->processTransactions();
132 },
133 this,
134 /* extraData= */ nullptr,
135 /* delay= */ Nanoseconds(0),
136 /* outTimerHandle= */ nullptr);
137
138 if (!success) {
139 LOGE("Could not defer callback to process transactions");
140 }
141 }
142
143 template <typename TransactionData, size_t kMaxTransactions>
144 void TransactionManager<TransactionData, kMaxTransactions>::
doCompleteTransactionLocked(Transaction & transaction)145 doCompleteTransactionLocked(Transaction &transaction) {
146 uint8_t errorCode = CHRE_ERROR_TIMEOUT;
147 if (transaction.errorCode.has_value()) {
148 errorCode = *transaction.errorCode;
149 }
150
151 bool success = kCompleteCallback(transaction.data, errorCode);
152 if (success) {
153 LOGI("Transaction %" PRIu32 " completed with error code: %" PRIu8,
154 transaction.id, errorCode);
155 } else {
156 LOGE("Could not complete transaction %" PRIu32, transaction.id);
157 }
158 }
159
160 template <typename TransactionData, size_t kMaxTransactions>
161 void TransactionManager<TransactionData, kMaxTransactions>::
doStartTransactionLocked(Transaction & transaction,size_t i,Nanoseconds now)162 doStartTransactionLocked(Transaction &transaction, size_t i,
163 Nanoseconds now) {
164 // Ensure only one pending transaction per unique cookie.
165 bool canStart = true;
166 for (size_t j = 0; j < mTransactions.size(); ++j) {
167 if (i != j && mTransactions[j].cookie == transaction.cookie &&
168 mTransactions[j].numCompletedStartCalls > 0) {
169 canStart = false;
170 break;
171 }
172 }
173 if (!canStart) {
174 return;
175 }
176
177 if (transaction.timeoutTime.toRawNanoseconds() != 0) {
178 transaction.timeoutTime = now + kTimeout;
179 }
180
181 bool success = kStartCallback(transaction.data);
182 if (success) {
183 LOGI("Transaction %" PRIu32 " started", transaction.id);
184 } else {
185 LOGE("Could not start transaction %" PRIu32, transaction.id);
186 }
187
188 ++transaction.numCompletedStartCalls;
189 transaction.nextRetryTime = now + kRetryWaitTime;
190 }
191
192 template <typename TransactionData, size_t kMaxTransactions>
193 uint32_t TransactionManager<TransactionData,
generatePseudoRandomId()194 kMaxTransactions>::generatePseudoRandomId() {
195 uint64_t data =
196 SystemTime::getMonotonicTime().toRawNanoseconds() +
197 static_cast<uint64_t>(SystemTime::getEstimatedHostTimeOffset());
198 uint32_t hash = fnv1a32Hash(reinterpret_cast<const uint8_t*>(&data),
199 sizeof(uint64_t));
200
201 // We mix the top 2 bits back into the middle of the hash to provide a value
202 // that leaves a gap of at least ~1 billion sequence numbers before
203 // overflowing a signed int32 (as used on the Java side).
204 constexpr uint32_t kMask = 0xC0000000;
205 constexpr uint32_t kShiftAmount = 17;
206 uint32_t extraBits = hash & kMask;
207 hash ^= extraBits >> kShiftAmount;
208 return hash & ~kMask;
209 }
210
211 template <typename TransactionData, size_t kMaxTransactions>
212 void TransactionManager<TransactionData,
processTransactions()213 kMaxTransactions>::processTransactions() {
214 if (mTimerHandle != CHRE_TIMER_INVALID) {
215 CHRE_ASSERT(kDeferCancelCallback(mTimerHandle));
216 mTimerHandle = CHRE_TIMER_INVALID;
217 }
218
219 Nanoseconds now = SystemTime::getMonotonicTime();
220 Nanoseconds nextExecutionTime(UINT64_MAX);
221
222 {
223 LockGuard<Mutex> lock(mMutex);
224 if (mTransactions.empty()) {
225 return;
226 }
227
228 // If a transaction is completed, it will be removed from the queue.
229 // The loop continues processing in this case as there may be another
230 // transaction that is ready to start with the same cookie that was
231 // blocked from starting by the completed transaction.
232 bool continueProcessing;
233 do {
234 continueProcessing = false;
235 for (size_t i = 0; i < mTransactions.size();) {
236 Transaction &transaction = mTransactions[i];
237 if ((transaction.timeoutTime.toRawNanoseconds() != 0 &&
238 transaction.timeoutTime <= now) ||
239 (transaction.nextRetryTime <= now &&
240 transaction.numCompletedStartCalls > kMaxNumRetries) ||
241 transaction.errorCode.has_value()) {
242 doCompleteTransactionLocked(transaction);
243 mTransactions.remove(i);
244 continueProcessing = true;
245 } else {
246 if (transaction.nextRetryTime <= now) {
247 doStartTransactionLocked(transaction, i, now);
248 }
249
250 nextExecutionTime =
251 std::min(nextExecutionTime, transaction.nextRetryTime);
252 if (transaction.timeoutTime.toRawNanoseconds() != 0) {
253 nextExecutionTime =
254 std::min(nextExecutionTime, transaction.timeoutTime);
255 }
256 ++i;
257 }
258 }
259 } while (continueProcessing);
260 }
261
262 Nanoseconds waitTime = nextExecutionTime - SystemTime::getMonotonicTime();
263 if (waitTime.toRawNanoseconds() > 0) {
264 kDeferCallback(
265 TransactionManager<TransactionData, kMaxTransactions>::onTimerFired,
266 /* data= */ this, /* extraData= */ nullptr, waitTime, &mTimerHandle);
267 CHRE_ASSERT(mTimerHandle != CHRE_TIMER_INVALID);
268 }
269 }
270
271 } // namespace chre
272
273 #endif // CHRE_UTIL_TRANSACTION_MANAGER_IMPL_H_
274