1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.content.browser; 6 7 import android.content.ComponentCallbacks2; 8 import android.content.Context; 9 import android.content.res.Configuration; 10 11 import androidx.collection.ArraySet; 12 13 import org.chromium.base.Log; 14 import org.chromium.base.library_loader.LibraryLoader; 15 import org.chromium.base.metrics.RecordHistogram; 16 import org.chromium.base.process_launcher.ChildProcessConnection; 17 18 import java.util.Iterator; 19 import java.util.Set; 20 21 /** 22 * Manages oom bindings used to bound child services. 23 * This object must only be accessed from the launcher thread. 24 */ 25 class BindingManager implements ComponentCallbacks2 { 26 private static final String TAG = "BindingManager"; 27 28 // Low reduce ratio of moderate binding. 29 private static final float MODERATE_BINDING_LOW_REDUCE_RATIO = 0.25f; 30 // High reduce ratio of moderate binding. 31 private static final float MODERATE_BINDING_HIGH_REDUCE_RATIO = 0.5f; 32 33 // Delays used when clearing moderate binding pool when onSentToBackground happens. 34 private static final long MODERATE_BINDING_POOL_CLEARER_DELAY_MILLIS = 10 * 1000; 35 36 private final Set<ChildProcessConnection> mConnections = new ArraySet<ChildProcessConnection>(); 37 // Can be -1 to mean no max size. 38 private final int mMaxSize; 39 private final Iterable<ChildProcessConnection> mRanking; 40 private final Runnable mDelayedClearer; 41 42 // If not null, this is a connection in |mConnections| that does not have a moderate binding 43 // added by BindingManager. 44 private ChildProcessConnection mWaivedConnection; 45 46 @Override onTrimMemory(final int level)47 public void onTrimMemory(final int level) { 48 LauncherThread.post(new Runnable() { 49 @Override 50 public void run() { 51 Log.i(TAG, "onTrimMemory: level=%d, size=%d", level, mConnections.size()); 52 if (mConnections.isEmpty()) { 53 return; 54 } 55 if (level <= TRIM_MEMORY_RUNNING_MODERATE) { 56 reduce(MODERATE_BINDING_LOW_REDUCE_RATIO); 57 } else if (level <= TRIM_MEMORY_RUNNING_LOW) { 58 reduce(MODERATE_BINDING_HIGH_REDUCE_RATIO); 59 } else if (level == TRIM_MEMORY_UI_HIDDEN) { 60 // This will be handled by |mDelayedClearer|. 61 return; 62 } else { 63 removeAllConnections(); 64 } 65 } 66 }); 67 } 68 69 @Override onLowMemory()70 public void onLowMemory() { 71 LauncherThread.post(new Runnable() { 72 @Override 73 public void run() { 74 Log.i(TAG, "onLowMemory: evict %d bindings", mConnections.size()); 75 removeAllConnections(); 76 } 77 }); 78 } 79 80 @Override onConfigurationChanged(Configuration configuration)81 public void onConfigurationChanged(Configuration configuration) {} 82 reduce(float reduceRatio)83 private void reduce(float reduceRatio) { 84 int oldSize = mConnections.size(); 85 int newSize = (int) (oldSize * (1f - reduceRatio)); 86 Log.i(TAG, "Reduce connections from %d to %d", oldSize, newSize); 87 removeOldConnections(oldSize - newSize); 88 assert mConnections.size() == newSize; 89 ensureLowestRankIsWaived(); 90 } 91 removeAllConnections()92 private void removeAllConnections() { 93 removeOldConnections(mConnections.size()); 94 } 95 removeOldConnections(int numberOfConnections)96 private void removeOldConnections(int numberOfConnections) { 97 assert numberOfConnections <= mConnections.size(); 98 int numRemoved = 0; 99 for (ChildProcessConnection connection : mRanking) { 100 if (mConnections.contains(connection)) { 101 removeModerateBindingIfNeeded(connection); 102 mConnections.remove(connection); 103 if (++numRemoved == numberOfConnections) break; 104 } 105 } 106 } 107 removeModerateBindingIfNeeded(ChildProcessConnection connection)108 private void removeModerateBindingIfNeeded(ChildProcessConnection connection) { 109 if (connection == mWaivedConnection) { 110 mWaivedConnection = null; 111 } else { 112 connection.removeModerateBinding(); 113 } 114 } 115 ensureLowestRankIsWaived()116 private void ensureLowestRankIsWaived() { 117 Iterator<ChildProcessConnection> itr = mRanking.iterator(); 118 if (!itr.hasNext()) return; 119 ChildProcessConnection lowestRanked = itr.next(); 120 121 if (lowestRanked == mWaivedConnection) return; 122 if (mWaivedConnection != null) { 123 assert mConnections.contains(mWaivedConnection); 124 mWaivedConnection.addModerateBinding(); 125 mWaivedConnection = null; 126 } 127 if (!mConnections.contains(lowestRanked)) return; 128 lowestRanked.removeModerateBinding(); 129 mWaivedConnection = lowestRanked; 130 } 131 132 /** 133 * Called when the embedding application is sent to background. 134 * The embedder needs to ensure that: 135 * - every onBroughtToForeground() is followed by onSentToBackground() 136 * - pairs of consecutive onBroughtToForeground() / onSentToBackground() calls do not overlap 137 */ onSentToBackground()138 void onSentToBackground() { 139 assert LauncherThread.runningOnLauncherThread(); 140 if (mConnections.isEmpty()) return; 141 LauncherThread.postDelayed(mDelayedClearer, MODERATE_BINDING_POOL_CLEARER_DELAY_MILLIS); 142 } 143 144 /** Called when the embedding application is brought to foreground. */ onBroughtToForeground()145 void onBroughtToForeground() { 146 assert LauncherThread.runningOnLauncherThread(); 147 LauncherThread.removeCallbacks(mDelayedClearer); 148 } 149 150 /** 151 * Construct instance without maxsize and can support arbitrary number of connections. 152 */ BindingManager(Context context, Iterable<ChildProcessConnection> ranking)153 BindingManager(Context context, Iterable<ChildProcessConnection> ranking) { 154 this(-1, ranking, context); 155 } 156 157 /** 158 * Construct instance with maxSize. 159 */ BindingManager(Context context, int maxSize, Iterable<ChildProcessConnection> ranking)160 BindingManager(Context context, int maxSize, Iterable<ChildProcessConnection> ranking) { 161 this(maxSize, ranking, context); 162 assert maxSize > 0; 163 } 164 BindingManager(int maxSize, Iterable<ChildProcessConnection> ranking, Context context)165 private BindingManager(int maxSize, Iterable<ChildProcessConnection> ranking, Context context) { 166 assert LauncherThread.runningOnLauncherThread(); 167 Log.i(TAG, "Moderate binding enabled: maxSize=%d", maxSize); 168 169 mMaxSize = maxSize; 170 mRanking = ranking; 171 assert mMaxSize > 0 || mMaxSize == -1; 172 173 mDelayedClearer = new Runnable() { 174 @Override 175 public void run() { 176 Log.i(TAG, "Release moderate connections: %d", mConnections.size()); 177 // Tests may not load the native library which is required for 178 // recording histograms. 179 if (LibraryLoader.getInstance().isInitialized()) { 180 RecordHistogram.recordCountHistogram( 181 "Android.ModerateBindingCount", mConnections.size()); 182 } 183 removeAllConnections(); 184 } 185 }; 186 187 // Note that it is safe to call Context.registerComponentCallbacks from a background 188 // thread. 189 context.registerComponentCallbacks(this); 190 } 191 addConnection(ChildProcessConnection connection)192 public void addConnection(ChildProcessConnection connection) { 193 assert LauncherThread.runningOnLauncherThread(); 194 195 // Note that the size of connections is currently fairly small (40). 196 // If it became bigger we should consider using an alternate data structure. 197 boolean alreadyInQueue = !mConnections.add(connection); 198 if (!alreadyInQueue) connection.addModerateBinding(); 199 assert mMaxSize == -1 || mConnections.size() <= mMaxSize; 200 } 201 removeConnection(ChildProcessConnection connection)202 public void removeConnection(ChildProcessConnection connection) { 203 assert LauncherThread.runningOnLauncherThread(); 204 boolean alreadyInQueue = mConnections.remove(connection); 205 if (alreadyInQueue) removeModerateBindingIfNeeded(connection); 206 assert !mConnections.contains(connection); 207 } 208 209 // Separate from other public methods so it allows client to update ranking after 210 // adding and removing connection. rankingChanged()211 public void rankingChanged() { 212 ensureLowestRankIsWaived(); 213 } 214 } 215