xref: /aosp_15_r20/system/chre/util/include/chre/util/system/transaction_manager_impl.h (revision 84e339476a462649f82315436d70fd732297a399)
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