diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 1eebd0127818e..f5d1c106705dc 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -153,11 +153,26 @@ public class BroadcastConstants { "bcast_extra_running_urgent_process_queues"; private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1; + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive urgent + * broadcast dispatches allowed before letting broadcasts in lower priority queue + * to be scheduled in order to avoid starvation. + */ public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES; private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES = "bcast_max_consecutive_urgent_dispatches"; private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3; + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive normal + * broadcast dispatches allowed before letting broadcasts in lower priority queue + * to be scheduled in order to avoid starvation. + */ + public int MAX_CONSECUTIVE_NORMAL_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES; + private static final String KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES = + "bcast_max_consecutive_normal_dispatches"; + private static final int DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES = 10; + /** * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts * to dispatch to a "running" process queue before we retire them back to @@ -341,6 +356,9 @@ public class BroadcastConstants { MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt( KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES, DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES); + MAX_CONSECUTIVE_NORMAL_DISPATCHES = getDeviceConfigInt( + KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES, + DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES); MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS); MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS, @@ -396,6 +414,10 @@ public class BroadcastConstants { TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println(); pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println(); pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println(); + pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES, + MAX_CONSECUTIVE_URGENT_DISPATCHES).println(); + pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES, + MAX_CONSECUTIVE_NORMAL_DISPATCHES).println(); pw.decreaseIndent(); pw.println(); } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 66d7fc92340a1..672392d46a356 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -152,6 +152,12 @@ class BroadcastProcessQueue { */ private int mActiveCountConsecutiveUrgent; + /** + * Number of consecutive normal broadcasts that have been dispatched + * since the last offload dispatch. + */ + private int mActiveCountConsecutiveNormal; + /** * Count of pending broadcasts of these various flavors. */ @@ -551,48 +557,63 @@ class BroadcastProcessQueue { * Will thrown an exception if there are no pending broadcasts; relies on * {@link #isEmpty()} being false. */ - SomeArgs removeNextBroadcast() { + private @Nullable SomeArgs removeNextBroadcast() { final ArrayDeque queue = queueForNextBroadcast(); if (queue == mPendingUrgent) { mActiveCountConsecutiveUrgent++; - } else { + } else if (queue == mPending) { mActiveCountConsecutiveUrgent = 0; + mActiveCountConsecutiveNormal++; + } else if (queue == mPendingOffload) { + mActiveCountConsecutiveUrgent = 0; + mActiveCountConsecutiveNormal = 0; } - return queue.removeFirst(); + return !isQueueEmpty(queue) ? queue.removeFirst() : null; } @Nullable ArrayDeque queueForNextBroadcast() { - ArrayDeque nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent; - ArrayDeque nextNormal = null; - if (!mPending.isEmpty()) { - nextNormal = mPending; - } else if (!mPendingOffload.isEmpty()) { - nextNormal = mPendingOffload; + final ArrayDeque nextNormal = queueForNextBroadcast( + mPending, mPendingOffload, + mActiveCountConsecutiveNormal, constants.MAX_CONSECUTIVE_NORMAL_DISPATCHES); + final ArrayDeque nextBroadcastQueue = queueForNextBroadcast( + mPendingUrgent, nextNormal, + mActiveCountConsecutiveUrgent, constants.MAX_CONSECUTIVE_URGENT_DISPATCHES); + return nextBroadcastQueue; + } + + private @Nullable ArrayDeque queueForNextBroadcast( + @Nullable ArrayDeque highPriorityQueue, + @Nullable ArrayDeque lowPriorityQueue, + int consecutiveHighPriorityCount, + int maxHighPriorityDispatchLimit) { + // nothing high priority pending, no further decisionmaking + if (isQueueEmpty(highPriorityQueue)) { + return lowPriorityQueue; } - // nothing urgent pending, no further decisionmaking - if (nextUrgent == null) { - return nextNormal; - } - // nothing but urgent pending, also no further decisionmaking - if (nextNormal == null) { - return nextUrgent; + // nothing but high priority pending, also no further decisionmaking + if (isQueueEmpty(lowPriorityQueue)) { + return highPriorityQueue; } - // Starvation mitigation: although we prioritize urgent broadcasts by default, - // we allow non-urgent deliveries to make steady progress even if urgent - // broadcasts are arriving faster than they can be dispatched. + // Starvation mitigation: although we prioritize high priority queues by default, + // we allow low priority queues to make steady progress even if broadcasts in + // high priority queue are arriving faster than they can be dispatched. // - // We do not try to defer to the next non-urgent broadcast if that broadcast + // We do not try to defer to the next broadcast in low priority queues if that broadcast // is ordered and still blocked on delivery to other recipients. - final SomeArgs nextNormalArgs = nextNormal.peekFirst(); - final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1; - final int nextNormalIndex = nextNormalArgs.argi1; - final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1; - final boolean canTakeNormal = - mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES - && rNormal.enqueueTime <= rUrgent.enqueueTime - && !blockedOnOrderedDispatch(rNormal, nextNormalIndex); - return canTakeNormal ? nextNormal : nextUrgent; + final SomeArgs nextLPArgs = lowPriorityQueue.peekFirst(); + final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1; + final int nextLPRecordIndex = nextLPArgs.argi1; + final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1; + final boolean isLPQueueEligible = + consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit + && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime + && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex); + return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue; + } + + private static boolean isQueueEmpty(@Nullable ArrayDeque queue) { + return (queue == null || queue.isEmpty()); } /** @@ -600,13 +621,13 @@ class BroadcastProcessQueue { */ @Nullable SomeArgs peekNextBroadcast() { ArrayDeque queue = queueForNextBroadcast(); - return (queue != null) ? queue.peekFirst() : null; + return !isQueueEmpty(queue) ? queue.peekFirst() : null; } @VisibleForTesting @Nullable BroadcastRecord peekNextBroadcastRecord() { ArrayDeque queue = queueForNextBroadcast(); - return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null; + return !isQueueEmpty(queue) ? (BroadcastRecord) queue.peekFirst().arg1 : null; } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index c87fd26fbe822..de09b19438396 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -591,6 +591,73 @@ public class BroadcastQueueModernImplTest { assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); } + /** + * Verify that offload broadcasts are not starved because of broadcasts in higher priority + * queues. + */ + @Test + public void testOffloadStarvation() { + final BroadcastOptions optInteractive = BroadcastOptions.makeBasic(); + optInteractive.setInteractive(true); + + mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 1; + mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2; + final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, + PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); + + // mix of broadcasts, with more than 2 normal + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES), + optInteractive), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE), + optInteractive), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL), + optInteractive), 0); + + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); + // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); + // and then back to prioritizing urgent ones + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction()); + // after MAX_CONSECUTIVE_URGENT_DISPATCHES, again an ordinary one next + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); + // and then back to prioritizing urgent ones + queue.makeActiveNextPending(); + assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, + queue.getActive().intent.getAction()); + // after MAX_CONSECUTIVE_URGENT_DISPATCHES and MAX_CONSECUTIVE_NORMAL_DISPATCHES, + // expect an offload one + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction()); + // and then back to prioritizing urgent ones + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction()); + } + /** * Verify that sending a broadcast that removes any matching pending * broadcasts is applied as expected.