xref: /aosp_15_r20/system/chre/util/include/chre/util/system/transaction_manager.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_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