diff options
3 files changed, 847 insertions, 216 deletions
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 3f4a9bb4d864..ed69f7ad32f6 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -444,8 +444,17 @@ public class ContextHubService extends IContextHubService.Stub { mSupportedContextHubPerms = hubInfo.second; mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values()); mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper); - mTransactionManager = new ContextHubTransactionManager( - mContextHubWrapper, mClientManager, mNanoAppStateManager); + + if (Flags.reduceLockingContextHubTransactionManager()) { + mTransactionManager = + new ContextHubTransactionManager( + mContextHubWrapper, mClientManager, mNanoAppStateManager); + } else { + mTransactionManager = + new ContextHubTransactionManagerOld( + mContextHubWrapper, mClientManager, mNanoAppStateManager); + } + mSensorPrivacyManagerInternal = LocalServices.getService(SensorPrivacyManagerInternal.class); return true; diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java index 2a0b1afde27b..da31bf29a8e8 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java @@ -26,6 +26,8 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import java.time.Duration; import java.util.ArrayDeque; import java.util.Collections; @@ -43,8 +45,8 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Manages transactions at the Context Hub Service. - * <p> - * This class maintains a queue of transaction requests made to the ContextHubService by clients, + * + * <p>This class maintains a queue of transaction requests made to the ContextHubService by clients, * and executes them through the Context Hub. At any point in time, either the transaction queue is * empty, or there is a pending transaction that is waiting for an asynchronous response from the * hub. This class also handles synchronous errors and timeouts of each transaction. @@ -52,66 +54,80 @@ import java.util.concurrent.atomic.AtomicInteger; * @hide */ /* package */ class ContextHubTransactionManager { - private static final String TAG = "ContextHubTransactionManager"; + protected static final String TAG = "ContextHubTransactionManager"; public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1); public static final Duration RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT = RELIABLE_MESSAGE_TIMEOUT.multipliedBy(3); - private static final int MAX_PENDING_REQUESTS = 10000; + // TODO: b/362299144: When cleaning up the flag + // reduce_locking_context_hub_transaction_manager, change these to private + protected static final int MAX_PENDING_REQUESTS = 10000; - private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3; + protected static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3; - private static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250); + protected static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250); - private static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000); + protected static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000); - private final IContextHubWrapper mContextHubProxy; + protected final IContextHubWrapper mContextHubProxy; - private final ContextHubClientManager mClientManager; + protected final ContextHubClientManager mClientManager; - private final NanoAppStateManager mNanoAppStateManager; + protected final NanoAppStateManager mNanoAppStateManager; - private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>(); + @GuardedBy("mTransactionLock") + protected final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>(); - private final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap = + @GuardedBy("mReliableMessageLock") + protected final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap = new HashMap<>(); /** A set of host endpoint IDs that have an active pending transaction. */ - private final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>(); + @GuardedBy("mReliableMessageLock") + protected final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>(); - private final AtomicInteger mNextAvailableId = new AtomicInteger(); + protected final AtomicInteger mNextAvailableId = new AtomicInteger(); /** - * The next available message sequence number. We choose a random - * number to start with to avoid collisions and limit the bound to - * half of the max value to avoid overflow. + * The next available message sequence number. We choose a random number to start with to avoid + * collisions and limit the bound to half of the max value to avoid overflow. */ - private final AtomicInteger mNextAvailableMessageSequenceNumber = + protected final AtomicInteger mNextAvailableMessageSequenceNumber = new AtomicInteger(new Random().nextInt(Integer.MAX_VALUE / 2)); /* - * An executor and the future object for scheduling timeout timers and + * An executor and the future objects for scheduling timeout timers and * for scheduling the processing of reliable message transactions. */ - private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1); - private ScheduledFuture<?> mTimeoutFuture = null; - private ScheduledFuture<?> mReliableMessageTransactionFuture = null; + protected final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(2); + + @GuardedBy("mTransactionLock") + protected ScheduledFuture<?> mTimeoutFuture = null; + + @GuardedBy("mReliableMessageLock") + protected ScheduledFuture<?> mReliableMessageTransactionFuture = null; /* * The list of previous transaction records. */ - private static final int NUM_TRANSACTION_RECORDS = 20; - private final ConcurrentLinkedEvictingDeque<TransactionRecord> mTransactionRecordDeque = + protected static final int NUM_TRANSACTION_RECORDS = 20; + protected final ConcurrentLinkedEvictingDeque<TransactionRecord> mTransactionRecordDeque = new ConcurrentLinkedEvictingDeque<>(NUM_TRANSACTION_RECORDS); - /** - * A container class to store a record of transactions. + /* + * Locks for synchronization of normal transactions separately from reliable message + * transactions. */ - private class TransactionRecord { - private final String mTransaction; - private final long mTimestamp; + protected final Object mTransactionLock = new Object(); + protected final Object mReliableMessageLock = new Object(); + protected final Object mTransactionRecordLock = new Object(); + + /** A container class to store a record of transactions. */ + protected static class TransactionRecord { + protected final String mTransaction; + protected final long mTimestamp; TransactionRecord(String transaction) { mTransaction = transaction; @@ -126,8 +142,18 @@ import java.util.concurrent.atomic.AtomicInteger; } } + /** Used when finishing a transaction. */ + interface TransactionAcceptConditions { + /** + * Returns whether to accept the found transaction when receiving a response from the + * Context Hub. + */ + boolean acceptTransaction(ContextHubServiceTransaction transaction); + } + /* package */ ContextHubTransactionManager( - IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, + IContextHubWrapper contextHubProxy, + ContextHubClientManager clientManager, NanoAppStateManager nanoAppStateManager) { mContextHubProxy = contextHubProxy; mClientManager = clientManager; @@ -409,34 +435,47 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Adds a new transaction to the queue. - * <p> - * If there was no pending transaction at the time, the transaction that was added will be + * + * <p>If there was no pending transaction at the time, the transaction that was added will be * started in this method. If there were too many transactions in the queue, an exception will * be thrown. * * @param transaction the transaction to add - * @throws IllegalStateException if the queue is full */ /* package */ - synchronized void addTransaction( - ContextHubServiceTransaction transaction) throws IllegalStateException { + void addTransaction(ContextHubServiceTransaction transaction) { if (transaction == null) { return; } - if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS - || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) { - throw new IllegalStateException("Transaction queue is full (capacity = " - + MAX_PENDING_REQUESTS + ")"); + synchronized (mTransactionRecordLock) { + mTransactionRecordDeque.add(new TransactionRecord(transaction.toString())); } - mTransactionRecordDeque.add(new TransactionRecord(transaction.toString())); if (Flags.reliableMessageRetrySupportService() && transaction.getTransactionType() == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) { - mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction); + synchronized (mReliableMessageLock) { + if (mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) { + throw new IllegalStateException( + "Reliable message transaction queue is full " + + "(capacity = " + + MAX_PENDING_REQUESTS + + ")"); + } + mReliableMessageTransactionMap.put( + transaction.getMessageSequenceNumber(), transaction); + } mExecutor.execute(() -> processMessageTransactions()); - } else { + return; + } + + synchronized (mTransactionLock) { + if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS) { + throw new IllegalStateException( + "Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")"); + } + mTransactionQueue.add(transaction); if (mTransactionQueue.size() == 1) { startNextTransaction(); @@ -448,62 +487,85 @@ import java.util.concurrent.atomic.AtomicInteger; * Handles a transaction response from a Context Hub. * * @param transactionId the transaction ID of the response - * @param success true if the transaction succeeded + * @param success true if the transaction succeeded */ /* package */ - synchronized void onTransactionResponse(int transactionId, boolean success) { - ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + void onTransactionResponse(int transactionId, boolean success) { + TransactionAcceptConditions conditions = + transaction -> transaction.getTransactionId() == transactionId; + ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions); if (transaction == null) { - Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); - return; - } - if (transaction.getTransactionId() != transactionId) { Log.w(TAG, "Received unexpected transaction response (expected ID = " - + transaction.getTransactionId() + ", received ID = " + transactionId + ")"); + + transactionId + + ", received ID = " + + transaction.getTransactionId() + + ")"); return; } - transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS : - ContextHubTransaction.RESULT_FAILED_AT_HUB); - removeTransactionAndStartNext(); + synchronized (transaction) { + transaction.onTransactionComplete( + success + ? ContextHubTransaction.RESULT_SUCCESS + : ContextHubTransaction.RESULT_FAILED_AT_HUB); + transaction.setComplete(); + } } + /** + * Handles a message delivery response from a Context Hub. + * + * @param messageSequenceNumber the message sequence number of the response + * @param success true if the message was delivered successfully + */ /* package */ - synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) { + void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) { if (!Flags.reliableMessageRetrySupportService()) { - ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + TransactionAcceptConditions conditions = + transaction -> transaction.getTransactionType() + == ContextHubTransaction.TYPE_RELIABLE_MESSAGE + && transaction.getMessageSequenceNumber() + == messageSequenceNumber; + ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions); if (transaction == null) { - Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); + Log.w(TAG, "Received unexpected message delivery response (expected" + + " message sequence number = " + + messageSequenceNumber + + ", received messageSequenceNumber = " + + messageSequenceNumber + + ")"); return; } - int transactionMessageSequenceNumber = transaction.getMessageSequenceNumber(); - if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE - || transactionMessageSequenceNumber != messageSequenceNumber) { - Log.w(TAG, "Received unexpected message transaction response (expected message " - + "sequence number = " - + transaction.getMessageSequenceNumber() - + ", received messageSequenceNumber = " + messageSequenceNumber + ")"); - return; + synchronized (transaction) { + transaction.onTransactionComplete( + success + ? ContextHubTransaction.RESULT_SUCCESS + : ContextHubTransaction.RESULT_FAILED_AT_HUB); + transaction.setComplete(); } - - transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS : - ContextHubTransaction.RESULT_FAILED_AT_HUB); - removeTransactionAndStartNext(); return; } - ContextHubServiceTransaction transaction = - mReliableMessageTransactionMap.get(messageSequenceNumber); - if (transaction == null) { - Log.w(TAG, "Could not find reliable message transaction with " - + "message sequence number = " - + messageSequenceNumber); - return; + ContextHubServiceTransaction transaction = null; + synchronized (mReliableMessageLock) { + transaction = mReliableMessageTransactionMap.get(messageSequenceNumber); + if (transaction == null) { + Log.w( + TAG, + "Could not find reliable message transaction with " + + "message sequence number = " + + messageSequenceNumber); + return; + } + + removeMessageTransaction(transaction); } - completeMessageTransaction(transaction, - success ? ContextHubTransaction.RESULT_SUCCESS + completeMessageTransaction( + transaction, + success + ? ContextHubTransaction.RESULT_SUCCESS : ContextHubTransaction.RESULT_FAILED_AT_HUB); mExecutor.execute(() -> processMessageTransactions()); } @@ -514,77 +576,116 @@ import java.util.concurrent.atomic.AtomicInteger; * @param nanoAppStateList the list of nanoapps included in the response */ /* package */ - synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) { - ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + void onQueryResponse(List<NanoAppState> nanoAppStateList) { + TransactionAcceptConditions conditions = transaction -> + transaction.getTransactionType() == ContextHubTransaction.TYPE_QUERY_NANOAPPS; + ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions); if (transaction == null) { - Log.w(TAG, "Received unexpected query response (no transaction pending)"); - return; - } - if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) { Log.w(TAG, "Received unexpected query response (expected " + transaction + ")"); return; } - transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList); - removeTransactionAndStartNext(); + synchronized (transaction) { + transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList); + transaction.setComplete(); + } } - /** - * Handles a hub reset event by stopping a pending transaction and starting the next. - */ + /** Handles a hub reset event by stopping a pending transaction and starting the next. */ /* package */ - synchronized void onHubReset() { + void onHubReset() { if (Flags.reliableMessageRetrySupportService()) { - Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = - mReliableMessageTransactionMap.entrySet().iterator(); - while (iter.hasNext()) { - completeMessageTransaction(iter.next().getValue(), - ContextHubTransaction.RESULT_FAILED_AT_HUB, iter); + synchronized (mReliableMessageLock) { + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = + mReliableMessageTransactionMap.entrySet().iterator(); + while (iter.hasNext()) { + removeAndCompleteMessageTransaction( + iter.next().getValue(), + ContextHubTransaction.RESULT_FAILED_AT_HUB, + iter); + } } } - ContextHubServiceTransaction transaction = mTransactionQueue.peek(); - if (transaction == null) { - return; + synchronized (mTransactionLock) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + return; + } + + removeTransactionAndStartNext(); } + } - removeTransactionAndStartNext(); + /** + * This function also starts the next transaction and removes the active transaction from the + * queue. The caller should complete the transaction. + * + * <p>Returns the active transaction if the transaction queue is not empty, the transaction is + * not null, and the transaction matches the conditions. + */ + private ContextHubServiceTransaction getTransactionAndHandleNext( + TransactionAcceptConditions conditions) { + ContextHubServiceTransaction transaction = null; + synchronized (mTransactionLock) { + transaction = mTransactionQueue.peek(); + if (transaction == null + || (conditions != null && !conditions.acceptTransaction(transaction))) { + return null; + } + + cancelTimeoutFuture(); + mTransactionQueue.remove(); + if (!mTransactionQueue.isEmpty()) { + startNextTransaction(); + } + } + return transaction; } /** * Pops the front transaction from the queue and starts the next pending transaction request. - * <p> - * Removing elements from the transaction queue must only be done through this method. When a + * + * <p>Removing elements from the transaction queue must only be done through this method. When a * pending transaction is removed, the timeout timer is cancelled and the transaction is marked * complete. - * <p> - * It is assumed that the transaction queue is non-empty when this method is invoked, and that - * the caller has obtained a lock on this ContextHubTransactionManager object. + * + * <p>It is assumed that the transaction queue is non-empty when this method is invoked, and + * that the caller has obtained mTransactionLock. */ + @GuardedBy("mTransactionLock") private void removeTransactionAndStartNext() { - if (mTimeoutFuture != null) { - mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false); - mTimeoutFuture = null; - } + cancelTimeoutFuture(); ContextHubServiceTransaction transaction = mTransactionQueue.remove(); - transaction.setComplete(); + synchronized (transaction) { + transaction.setComplete(); + } if (!mTransactionQueue.isEmpty()) { startNextTransaction(); } } + /** Cancels the timeout future. */ + @GuardedBy("mTransactionLock") + private void cancelTimeoutFuture() { + if (mTimeoutFuture != null) { + mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false); + mTimeoutFuture = null; + } + } + /** * Starts the next pending transaction request. - * <p> - * Starting new transactions must only be done through this method. This method continues to + * + * <p>Starting new transactions must only be done through this method. This method continues to * process the transaction queue as long as there are pending requests, and no transaction is * pending. - * <p> - * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager - * object. + * + * <p>It is assumed that the caller has obtained a lock on mTransactionLock. */ + @GuardedBy("mTransactionLock") private void startNextTransaction() { int result = ContextHubTransaction.RESULT_FAILED_UNKNOWN; while (result != ContextHubTransaction.RESULT_SUCCESS && !mTransactionQueue.isEmpty()) { @@ -592,28 +693,36 @@ import java.util.concurrent.atomic.AtomicInteger; result = transaction.onTransact(); if (result == ContextHubTransaction.RESULT_SUCCESS) { - Runnable onTimeoutFunc = () -> { - synchronized (this) { - if (!transaction.isComplete()) { - Log.d(TAG, transaction + " timed out"); - transaction.onTransactionComplete( - ContextHubTransaction.RESULT_FAILED_TIMEOUT); - - removeTransactionAndStartNext(); - } - } - }; + Runnable onTimeoutFunc = + () -> { + synchronized (transaction) { + if (!transaction.isComplete()) { + Log.d(TAG, transaction + " timed out"); + transaction.onTransactionComplete( + ContextHubTransaction.RESULT_FAILED_TIMEOUT); + transaction.setComplete(); + } + } + + synchronized (mTransactionLock) { + removeTransactionAndStartNext(); + } + }; long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS); try { - mTimeoutFuture = mExecutor.schedule( - onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS); + mTimeoutFuture = + mExecutor.schedule(onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS); } catch (Exception e) { Log.e(TAG, "Error when schedule a timer", e); } } else { - transaction.onTransactionComplete( - ContextHubServiceUtil.toTransactionResult(result)); + synchronized (transaction) { + transaction.onTransactionComplete( + ContextHubServiceUtil.toTransactionResult(result)); + transaction.setComplete(); + } + mTransactionQueue.remove(); } } @@ -621,81 +730,97 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Processes message transactions, starting and completing them as needed. - * <p> - * This function is called when adding a message transaction or when a timer - * expires for an existing message transaction's retry or timeout. The - * internal processing loop will iterate at most twice as if one iteration - * completes a transaction, the next iteration can only start new transactions. - * If the first iteration does not complete any transaction, the loop will - * only iterate once. + * + * <p>This function is called when adding a message transaction or when a timer expires for an + * existing message transaction's retry or timeout. The internal processing loop will iterate at + * most twice as if one iteration completes a transaction, the next iteration can only start new + * transactions. If the first iteration does not complete any transaction, the loop will only + * iterate once. + * * <p> */ - private synchronized void processMessageTransactions() { - if (!Flags.reliableMessageRetrySupportService()) { - return; - } - - if (mReliableMessageTransactionFuture != null) { - mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false); - mReliableMessageTransactionFuture = null; - } + private void processMessageTransactions() { + synchronized (mReliableMessageLock) { + if (!Flags.reliableMessageRetrySupportService()) { + return; + } - long now = SystemClock.elapsedRealtimeNanos(); - long nextExecutionTime = Long.MAX_VALUE; - boolean continueProcessing; - do { - continueProcessing = false; - Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = - mReliableMessageTransactionMap.entrySet().iterator(); - while (iter.hasNext()) { - ContextHubServiceTransaction transaction = iter.next().getValue(); - short hostEndpointId = transaction.getHostEndpointId(); - int numCompletedStartCalls = transaction.getNumCompletedStartCalls(); - if (numCompletedStartCalls == 0 - && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) { - continue; - } + if (mReliableMessageTransactionFuture != null) { + mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false); + mReliableMessageTransactionFuture = null; + } - long nextRetryTime = transaction.getNextRetryTime(); - long timeoutTime = transaction.getTimeoutTime(); - boolean transactionTimedOut = timeoutTime <= now; - boolean transactionHitMaxRetries = nextRetryTime <= now - && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY; - if (transactionTimedOut || transactionHitMaxRetries) { - completeMessageTransaction(transaction, - ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter); - continueProcessing = true; - } else { - if (nextRetryTime <= now || numCompletedStartCalls <= 0) { - startMessageTransaction(transaction, now); + long now = SystemClock.elapsedRealtimeNanos(); + long nextExecutionTime = Long.MAX_VALUE; + boolean continueProcessing; + do { + continueProcessing = false; + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = + mReliableMessageTransactionMap.entrySet().iterator(); + while (iter.hasNext()) { + ContextHubServiceTransaction transaction = iter.next().getValue(); + short hostEndpointId = transaction.getHostEndpointId(); + int numCompletedStartCalls = transaction.getNumCompletedStartCalls(); + if (numCompletedStartCalls == 0 + && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) { + continue; } - nextExecutionTime = Math.min(nextExecutionTime, - transaction.getNextRetryTime()); - nextExecutionTime = Math.min(nextExecutionTime, - transaction.getTimeoutTime()); + long nextRetryTime = transaction.getNextRetryTime(); + long timeoutTime = transaction.getTimeoutTime(); + boolean transactionTimedOut = timeoutTime <= now; + boolean transactionHitMaxRetries = + nextRetryTime <= now + && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY; + if (transactionTimedOut || transactionHitMaxRetries) { + removeAndCompleteMessageTransaction( + transaction, ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter); + continueProcessing = true; + } else { + if (nextRetryTime <= now || numCompletedStartCalls <= 0) { + startMessageTransaction(transaction, now); + } + + nextExecutionTime = + Math.min(nextExecutionTime, transaction.getNextRetryTime()); + nextExecutionTime = + Math.min(nextExecutionTime, transaction.getTimeoutTime()); + } } + } while (continueProcessing); + + if (nextExecutionTime < Long.MAX_VALUE) { + mReliableMessageTransactionFuture = + mExecutor.schedule( + () -> processMessageTransactions(), + Math.max( + nextExecutionTime - SystemClock.elapsedRealtimeNanos(), + RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()), + TimeUnit.NANOSECONDS); } - } while (continueProcessing); - - if (nextExecutionTime < Long.MAX_VALUE) { - mReliableMessageTransactionFuture = mExecutor.schedule( - () -> processMessageTransactions(), - Math.max(nextExecutionTime - SystemClock.elapsedRealtimeNanos(), - RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()), - TimeUnit.NANOSECONDS); } } /** - * Completes a message transaction and removes it from the reliable message map. + * Completes a message transaction. * * @param transaction The transaction to complete. * @param result The result code. */ - private void completeMessageTransaction(ContextHubServiceTransaction transaction, - @ContextHubTransaction.Result int result) { - completeMessageTransaction(transaction, result, /* iter= */ null); + private void completeMessageTransaction( + ContextHubServiceTransaction transaction, @ContextHubTransaction.Result int result) { + synchronized (transaction) { + transaction.onTransactionComplete(result); + transaction.setComplete(); + } + + Log.d( + TAG, + "Successfully completed reliable message transaction with " + + "message sequence number = " + + transaction.getMessageSequenceNumber() + + " and result = " + + result); } /** @@ -705,25 +830,41 @@ import java.util.concurrent.atomic.AtomicInteger; * @param result The result code. * @param iter The iterator for the reliable message map - used to remove the message directly. */ - private void completeMessageTransaction(ContextHubServiceTransaction transaction, + @GuardedBy("mReliableMessageLock") + private void removeAndCompleteMessageTransaction( + ContextHubServiceTransaction transaction, @ContextHubTransaction.Result int result, Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) { - transaction.onTransactionComplete(result); + removeMessageTransaction(transaction, iter); + completeMessageTransaction(transaction, result); + } + /** + * Removes a message transaction from the reliable message map. + * + * @param transaction The transaction to remove. + */ + @GuardedBy("mReliableMessageLock") + private void removeMessageTransaction(ContextHubServiceTransaction transaction) { + removeMessageTransaction(transaction, /* iter= */ null); + } + + /** + * Removes a message transaction from the reliable message map. + * + * @param transaction The transaction to remove. + * @param iter The iterator for the reliable message map - used to remove the message directly. + */ + @GuardedBy("mReliableMessageLock") + private void removeMessageTransaction( + ContextHubServiceTransaction transaction, + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) { if (iter == null) { mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber()); } else { iter.remove(); } mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId()); - - Log.d( - TAG, - "Successfully completed reliable message transaction with " - + "message sequence number = " - + transaction.getMessageSequenceNumber() - + " and result = " - + result); } /** @@ -732,24 +873,25 @@ import java.util.concurrent.atomic.AtomicInteger; * @param transaction The transaction to start. * @param now The now time. */ + @GuardedBy("mReliableMessageLock") private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) { int numCompletedStartCalls = transaction.getNumCompletedStartCalls(); @ContextHubTransaction.Result int result = transaction.onTransact(); if (result == ContextHubTransaction.RESULT_SUCCESS) { - Log.d( - TAG, - "Successfully " - + (numCompletedStartCalls == 0 ? "started" : "retried") - + " reliable message transaction with message sequence number = " - + transaction.getMessageSequenceNumber()); + Log.d( + TAG, + "Successfully " + + (numCompletedStartCalls == 0 ? "started" : "retried") + + " reliable message transaction with message sequence number = " + + transaction.getMessageSequenceNumber()); } else { - Log.w( - TAG, - "Could not start reliable message transaction with " - + "message sequence number = " - + transaction.getMessageSequenceNumber() - + ", result = " - + result); + Log.w( + TAG, + "Could not start reliable message transaction with " + + "message sequence number = " + + transaction.getMessageSequenceNumber() + + ", result = " + + result); } transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos()); @@ -788,17 +930,19 @@ import java.util.concurrent.atomic.AtomicInteger; public String toString() { StringBuilder sb = new StringBuilder(); int i = 0; - synchronized (this) { - for (ContextHubServiceTransaction transaction: mTransactionQueue) { + synchronized (mTransactionLock) { + for (ContextHubServiceTransaction transaction : mTransactionQueue) { sb.append(i); sb.append(": "); sb.append(transaction.toString()); sb.append("\n"); ++i; } + } - if (Flags.reliableMessageRetrySupportService()) { - for (ContextHubServiceTransaction transaction: + if (Flags.reliableMessageRetrySupportService()) { + synchronized (mReliableMessageLock) { + for (ContextHubServiceTransaction transaction : mReliableMessageTransactionMap.values()) { sb.append(i); sb.append(": "); @@ -807,7 +951,9 @@ import java.util.concurrent.atomic.AtomicInteger; ++i; } } + } + synchronized (mTransactionRecordLock) { sb.append("Transaction History:\n"); Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator(); while (iterator.hasNext()) { @@ -815,6 +961,7 @@ import java.util.concurrent.atomic.AtomicInteger; sb.append("\n"); } } + return sb.toString(); } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java new file mode 100644 index 000000000000..a67fa308a6ea --- /dev/null +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.contexthub; + +import android.chre.flags.Flags; +import android.hardware.location.ContextHubTransaction; +import android.hardware.location.IContextHubTransactionCallback; +import android.hardware.location.NanoAppBinary; +import android.hardware.location.NanoAppMessage; +import android.hardware.location.NanoAppState; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import java.time.Duration; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Manages transactions at the Context Hub Service. + * + * <p>This class maintains a queue of transaction requests made to the ContextHubService by clients, + * and executes them through the Context Hub. At any point in time, either the transaction queue is + * empty, or there is a pending transaction that is waiting for an asynchronous response from the + * hub. This class also handles synchronous errors and timeouts of each transaction. + * + * <p>This is the old version of ContextHubTransactionManager that uses global synchronization + * instead of individual locks. This will be deleted when the + * reduce_locking_context_hub_transaction_manager flag is cleaned up. + * + * @hide + */ +/* package */ class ContextHubTransactionManagerOld extends ContextHubTransactionManager { + /* package */ ContextHubTransactionManagerOld( + IContextHubWrapper contextHubProxy, + ContextHubClientManager clientManager, + NanoAppStateManager nanoAppStateManager) { + super(contextHubProxy, clientManager, nanoAppStateManager); + } + + /** + * Adds a new transaction to the queue. + * + * <p>If there was no pending transaction at the time, the transaction that was added will be + * started in this method. If there were too many transactions in the queue, an exception will + * be thrown. + * + * @param transaction the transaction to add + * @throws IllegalStateException if the queue is full + */ + /* package */ + @Override + synchronized void addTransaction(ContextHubServiceTransaction transaction) + throws IllegalStateException { + if (transaction == null) { + return; + } + + if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS + || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) { + throw new IllegalStateException( + "Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")"); + } + + mTransactionRecordDeque.add(new TransactionRecord(transaction.toString())); + if (Flags.reliableMessageRetrySupportService() + && transaction.getTransactionType() + == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) { + mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction); + mExecutor.execute(() -> processMessageTransactions()); + } else { + mTransactionQueue.add(transaction); + if (mTransactionQueue.size() == 1) { + startNextTransaction(); + } + } + } + + /** + * Handles a transaction response from a Context Hub. + * + * @param transactionId the transaction ID of the response + * @param success true if the transaction succeeded + */ + /* package */ + @Override + synchronized void onTransactionResponse(int transactionId, boolean success) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); + return; + } + if (transaction.getTransactionId() != transactionId) { + Log.w( + TAG, + "Received unexpected transaction response (expected ID = " + + transaction.getTransactionId() + + ", received ID = " + + transactionId + + ")"); + return; + } + + transaction.onTransactionComplete( + success + ? ContextHubTransaction.RESULT_SUCCESS + : ContextHubTransaction.RESULT_FAILED_AT_HUB); + removeTransactionAndStartNext(); + } + + /* package */ + @Override + synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) { + if (!Flags.reliableMessageRetrySupportService()) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); + return; + } + + int transactionMessageSequenceNumber = transaction.getMessageSequenceNumber(); + if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE + || transactionMessageSequenceNumber != messageSequenceNumber) { + Log.w( + TAG, + "Received unexpected message transaction response (expected message " + + "sequence number = " + + transaction.getMessageSequenceNumber() + + ", received messageSequenceNumber = " + + messageSequenceNumber + + ")"); + return; + } + + transaction.onTransactionComplete( + success + ? ContextHubTransaction.RESULT_SUCCESS + : ContextHubTransaction.RESULT_FAILED_AT_HUB); + removeTransactionAndStartNext(); + return; + } + + ContextHubServiceTransaction transaction = + mReliableMessageTransactionMap.get(messageSequenceNumber); + if (transaction == null) { + Log.w( + TAG, + "Could not find reliable message transaction with " + + "message sequence number = " + + messageSequenceNumber); + return; + } + + completeMessageTransaction( + transaction, + success + ? ContextHubTransaction.RESULT_SUCCESS + : ContextHubTransaction.RESULT_FAILED_AT_HUB); + mExecutor.execute(() -> processMessageTransactions()); + } + + /** + * Handles a query response from a Context Hub. + * + * @param nanoAppStateList the list of nanoapps included in the response + */ + /* package */ + @Override + synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + Log.w(TAG, "Received unexpected query response (no transaction pending)"); + return; + } + if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) { + Log.w(TAG, "Received unexpected query response (expected " + transaction + ")"); + return; + } + + transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList); + removeTransactionAndStartNext(); + } + + /** Handles a hub reset event by stopping a pending transaction and starting the next. */ + /* package */ + @Override + synchronized void onHubReset() { + if (Flags.reliableMessageRetrySupportService()) { + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = + mReliableMessageTransactionMap.entrySet().iterator(); + while (iter.hasNext()) { + completeMessageTransaction( + iter.next().getValue(), ContextHubTransaction.RESULT_FAILED_AT_HUB, iter); + } + } + + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + return; + } + + removeTransactionAndStartNext(); + } + + /** + * Pops the front transaction from the queue and starts the next pending transaction request. + * + * <p>Removing elements from the transaction queue must only be done through this method. When a + * pending transaction is removed, the timeout timer is cancelled and the transaction is marked + * complete. + * + * <p>It is assumed that the transaction queue is non-empty when this method is invoked, and + * that the caller has obtained a lock on this ContextHubTransactionManager object. + */ + private void removeTransactionAndStartNext() { + if (mTimeoutFuture != null) { + mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false); + mTimeoutFuture = null; + } + + ContextHubServiceTransaction transaction = mTransactionQueue.remove(); + transaction.setComplete(); + + if (!mTransactionQueue.isEmpty()) { + startNextTransaction(); + } + } + + /** + * Starts the next pending transaction request. + * + * <p>Starting new transactions must only be done through this method. This method continues to + * process the transaction queue as long as there are pending requests, and no transaction is + * pending. + * + * <p>It is assumed that the caller has obtained a lock on this ContextHubTransactionManager + * object. + */ + private void startNextTransaction() { + int result = ContextHubTransaction.RESULT_FAILED_UNKNOWN; + while (result != ContextHubTransaction.RESULT_SUCCESS && !mTransactionQueue.isEmpty()) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + result = transaction.onTransact(); + + if (result == ContextHubTransaction.RESULT_SUCCESS) { + Runnable onTimeoutFunc = + () -> { + synchronized (this) { + if (!transaction.isComplete()) { + Log.d(TAG, transaction + " timed out"); + transaction.onTransactionComplete( + ContextHubTransaction.RESULT_FAILED_TIMEOUT); + + removeTransactionAndStartNext(); + } + } + }; + + long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS); + try { + mTimeoutFuture = + mExecutor.schedule(onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS); + } catch (Exception e) { + Log.e(TAG, "Error when schedule a timer", e); + } + } else { + transaction.onTransactionComplete( + ContextHubServiceUtil.toTransactionResult(result)); + mTransactionQueue.remove(); + } + } + } + + /** + * Processes message transactions, starting and completing them as needed. + * + * <p>This function is called when adding a message transaction or when a timer expires for an + * existing message transaction's retry or timeout. The internal processing loop will iterate at + * most twice as if one iteration completes a transaction, the next iteration can only start new + * transactions. If the first iteration does not complete any transaction, the loop will only + * iterate once. + * + * <p> + */ + private synchronized void processMessageTransactions() { + if (!Flags.reliableMessageRetrySupportService()) { + return; + } + + if (mReliableMessageTransactionFuture != null) { + mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false); + mReliableMessageTransactionFuture = null; + } + + long now = SystemClock.elapsedRealtimeNanos(); + long nextExecutionTime = Long.MAX_VALUE; + boolean continueProcessing; + do { + continueProcessing = false; + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = + mReliableMessageTransactionMap.entrySet().iterator(); + while (iter.hasNext()) { + ContextHubServiceTransaction transaction = iter.next().getValue(); + short hostEndpointId = transaction.getHostEndpointId(); + int numCompletedStartCalls = transaction.getNumCompletedStartCalls(); + if (numCompletedStartCalls == 0 + && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) { + continue; + } + + long nextRetryTime = transaction.getNextRetryTime(); + long timeoutTime = transaction.getTimeoutTime(); + boolean transactionTimedOut = timeoutTime <= now; + boolean transactionHitMaxRetries = + nextRetryTime <= now + && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY; + if (transactionTimedOut || transactionHitMaxRetries) { + completeMessageTransaction( + transaction, ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter); + continueProcessing = true; + } else { + if (nextRetryTime <= now || numCompletedStartCalls <= 0) { + startMessageTransaction(transaction, now); + } + + nextExecutionTime = Math.min(nextExecutionTime, transaction.getNextRetryTime()); + nextExecutionTime = Math.min(nextExecutionTime, transaction.getTimeoutTime()); + } + } + } while (continueProcessing); + + if (nextExecutionTime < Long.MAX_VALUE) { + mReliableMessageTransactionFuture = + mExecutor.schedule( + () -> processMessageTransactions(), + Math.max( + nextExecutionTime - SystemClock.elapsedRealtimeNanos(), + RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()), + TimeUnit.NANOSECONDS); + } + } + + /** + * Completes a message transaction and removes it from the reliable message map. + * + * @param transaction The transaction to complete. + * @param result The result code. + */ + private void completeMessageTransaction( + ContextHubServiceTransaction transaction, @ContextHubTransaction.Result int result) { + completeMessageTransaction(transaction, result, /* iter= */ null); + } + + /** + * Completes a message transaction and removes it from the reliable message map using iter. + * + * @param transaction The transaction to complete. + * @param result The result code. + * @param iter The iterator for the reliable message map - used to remove the message directly. + */ + private void completeMessageTransaction( + ContextHubServiceTransaction transaction, + @ContextHubTransaction.Result int result, + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) { + transaction.onTransactionComplete(result); + + if (iter == null) { + mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber()); + } else { + iter.remove(); + } + mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId()); + + Log.d( + TAG, + "Successfully completed reliable message transaction with " + + "message sequence number = " + + transaction.getMessageSequenceNumber() + + " and result = " + + result); + } + + /** + * Starts a message transaction. + * + * @param transaction The transaction to start. + * @param now The now time. + */ + private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) { + int numCompletedStartCalls = transaction.getNumCompletedStartCalls(); + @ContextHubTransaction.Result int result = transaction.onTransact(); + if (result == ContextHubTransaction.RESULT_SUCCESS) { + Log.d( + TAG, + "Successfully " + + (numCompletedStartCalls == 0 ? "started" : "retried") + + " reliable message transaction with message sequence number = " + + transaction.getMessageSequenceNumber()); + } else { + Log.w( + TAG, + "Could not start reliable message transaction with " + + "message sequence number = " + + transaction.getMessageSequenceNumber() + + ", result = " + + result); + } + + transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos()); + if (transaction.getTimeoutTime() == Long.MAX_VALUE) { // first time starting transaction + transaction.setTimeoutTime(now + RELIABLE_MESSAGE_TIMEOUT.toNanos()); + } + transaction.setNumCompletedStartCalls(numCompletedStartCalls + 1); + mReliableMessageHostEndpointIdActiveSet.add(transaction.getHostEndpointId()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int i = 0; + synchronized (this) { + for (ContextHubServiceTransaction transaction : mTransactionQueue) { + sb.append(i); + sb.append(": "); + sb.append(transaction.toString()); + sb.append("\n"); + ++i; + } + + if (Flags.reliableMessageRetrySupportService()) { + for (ContextHubServiceTransaction transaction : + mReliableMessageTransactionMap.values()) { + sb.append(i); + sb.append(": "); + sb.append(transaction.toString()); + sb.append("\n"); + ++i; + } + } + + sb.append("Transaction History:\n"); + Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator(); + while (iterator.hasNext()) { + sb.append(iterator.next()); + sb.append("\n"); + } + } + return sb.toString(); + } +} |