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_H_ 18 #define CHRE_UTIL_SYSTEM_TRANSACTION_MANAGER_H_ 19 20 #include <cstdint> 21 22 #include "chre/util/array_queue.h" 23 #include "chre/util/non_copyable.h" 24 #include "chre/util/optional.h" 25 #include "chre/util/time.h" 26 27 namespace chre { 28 29 class TransactionManagerCallback { 30 public: 31 virtual ~TransactionManagerCallback() = default; 32 33 //! Initiate or retry an operation associated with the given transaction ID 34 virtual void onTransactionAttempt(uint32_t transactionId, 35 uint16_t groupId) = 0; 36 37 //! Invoked when a transaction fails to complete after the max attempt limit 38 virtual void onTransactionFailure(uint32_t transactionId, 39 uint16_t groupId) = 0; 40 }; 41 42 /** 43 * TransactionManager helps track operations which should be retried if not 44 * completed within a set amount of time. 45 * 46 * Transactions are long running operations identified by an ID. Further, 47 * transactions can be grouped to ensure that only one transaction within a 48 * group is outstanding at a time. 49 * 50 * This class is not thread-safe, so the caller must ensure all its methods are 51 * invoked on the same thread that TimerPool callbacks are invoked on. 52 * 53 * Usage summary: 54 * - Call add() to initiate the transaction and assign an ID 55 * - TransactionManager will invoke the onTransactionAttempt() callback, 56 * either synchronously and immediately, or after any previous transactions 57 * in the same group have completed 58 * - Call remove() when the transaction's operation completes or is canceled 59 * - If not called within the timeout, TransactionManager will call 60 * onTransactionAttempt() again 61 * - If the operation times out after the specified maximum number of 62 * attempts, TransactionManager will call onTransactionFailure() and 63 * remove the transaction (note that this is the only circumstance under 64 * which onTransactionFailure() is called) 65 * 66 * @param kMaxTransactions The maximum number of pending transactions 67 * (statically allocated) 68 * @param TimerPoolType A chre::TimerPool-like class, which supports methods 69 * with the same signature and semantics as TimerPool::setSystemTimer() and 70 * TimerPool::cancelSystemTimer() 71 */ 72 template <size_t kMaxTransactions, class TimerPoolType> 73 class TransactionManager : public NonCopyable { 74 public: 75 /** 76 * @param cb Callback 77 * @param timerPool TimerPool-like object to use for retry timers 78 * @param timeout How long to wait for remove() to be called after 79 * onTransactionAttempt() before trying again or failing 80 * @param maxAttempts Maximum number of times to try the transaction before 81 * giving up 82 */ 83 TransactionManager(TransactionManagerCallback &cb, TimerPoolType &timerPool, 84 Nanoseconds timeout, uint8_t maxAttempts = 3) kTimeout(timeout)85 : kTimeout(timeout), 86 kMaxAttempts(maxAttempts), 87 mTimerPool(timerPool), 88 mCb(cb) { 89 CHRE_ASSERT(timeout.toRawNanoseconds() > 0); 90 } 91 92 /** 93 * This destructor only guarantees that no transaction callbacks will be 94 * invoked after it returns – it does not invoke any callbacks on its own. 95 * Users of this class should typically ensure that all pending transactions 96 * are cleaned up (i.e. removed) prior to destroying this object. 97 */ 98 ~TransactionManager(); 99 100 /** 101 * Initiate a transaction, assigning it a globally unique transactionId and 102 * invoking the onTransactionAttempt() callback from within this function if 103 * it is the only pending transaction in the groupId. 104 * 105 * This must not be called from within a callback method, like 106 * onTransactionFailed(). 107 * 108 * @param groupId ID used to serialize groups of transactions 109 * @param[out] transactionId Assigned ID, set prior to calling 110 * onTransactionAttempt() 111 * @return false if kMaxTransactions are pending, true otherwise 112 */ 113 bool add(uint16_t groupId, uint32_t *transactionId); 114 115 /** 116 * Complete a transaction, by removing it from the active set of transactions. 117 * 118 * After this returns, it is guaranteed that callbacks will not be invoked for 119 * this transaction ID. If another transaction is pending with the same group 120 * ID as this one, onTransactionAttempt() is invoked for it from within this 121 * function. 122 * 123 * This should be called on successful completion or cancelation of a 124 * transaction, but is automatically handled when a transaction fails due to 125 * timeout. 126 * 127 * This must not be called from within a callback method, like 128 * onTransactionAttempt(). 129 * 130 * @param transactionId 131 * @return true if the transactionId was found and removed from the queue 132 */ 133 bool remove(uint32_t transactionId); 134 135 private: 136 //! Stores transaction-related data. 137 struct Transaction { TransactionTransaction138 Transaction(uint32_t id_, uint16_t groupId_) : id(id_), groupId(groupId_) {} 139 140 uint32_t id; 141 uint16_t groupId; 142 143 //! Counts up by 1 on each attempt, 0 when pending first attempt 144 uint8_t attemptCount = 0; 145 146 //! Absolute time when the next retry should be attempted or the transaction 147 //! should be considered failed. Defaults to max so it's never the next 148 //! timeout if something else is active. 149 Nanoseconds timeout = Nanoseconds(UINT64_MAX); 150 }; 151 152 //! RAII helper to set a boolean to true and restore to false at end of scope 153 class ScopedFlag { 154 public: ScopedFlag(bool & flag)155 ScopedFlag(bool &flag) : mFlag(flag) { 156 mFlag = true; 157 } ~ScopedFlag()158 ~ScopedFlag() { 159 mFlag = false; 160 } 161 162 private: 163 bool &mFlag; 164 }; 165 166 const Nanoseconds kTimeout; 167 const uint8_t kMaxAttempts; 168 169 TimerPoolType &mTimerPool; 170 TransactionManagerCallback &mCb; 171 172 //! Delayed assignment to start at a pseudo-random value 173 Optional<uint32_t> mNextTransactionId; 174 175 //! Helps catch misuse, e.g. trying to remove a transaction from a callback 176 bool mInCallback = false; 177 178 //! Handle of timer that expires, or CHRE_TIMER_INVALID if none 179 uint32_t mTimerHandle = CHRE_TIMER_INVALID; 180 181 //! Set of active transactions 182 ArrayQueue<Transaction, kMaxTransactions> mTransactions; 183 184 //! Callback given to mTimerPool, invoked when the next expiring transaction 185 //! has timed out onTimerExpired(uint16_t,void * data,void *)186 static void onTimerExpired(uint16_t /*type*/, void *data, 187 void * /*extraData*/) { 188 auto *obj = 189 static_cast<TransactionManager<kMaxTransactions, TimerPoolType> *>( 190 data); 191 obj->handleTimerExpiry(); 192 } 193 194 //! @return a pseudorandom ID for a transaction in the range of [0, 2^30 - 1] 195 uint32_t generatePseudoRandomId(); 196 197 //! If the last added transaction is the only one in its group, start it; 198 //! otherwise do nothing 199 void maybeStartLastTransaction(); 200 201 //! If there's a pending transaction in this group, start the next one; 202 //! otherwise do nothing 203 void startNextTransactionInGroup(uint16_t groupId); 204 205 //! Update the transaction state and invoke the attempt callback, but doesn't 206 //! set the timer 207 void startTransaction(Transaction &transaction); 208 209 //! Updates the timer to the proper state for mTransactions 210 void updateTimer(); 211 212 //! Sets the timer to expire after a delay 213 void setTimer(Nanoseconds delay); 214 215 //! Sets the timer to expire at the given time, or effectively immediately if 216 //! expiry is in the past 217 void setTimerAbsolute(Nanoseconds expiry); 218 219 //! Processes any timed out transactions and reset the timer as needed 220 void handleTimerExpiry(); 221 222 //! Invokes the failure callback and starts the next transaction in the group, 223 //! but does not remove the transaction (it should already be removed) 224 void handleTransactionFailure(Transaction &transaction); 225 }; 226 227 } // namespace chre 228 229 #include "chre/util/system/transaction_manager_impl.h" // IWYU pragma: export 230 231 #endif // CHRE_UTIL_SYSTEM_TRANSACTION_MANAGER_H_ 232