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