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.util.Log; 8 import android.util.SparseArray; 9 10 import com.google.common.annotations.VisibleForTesting; 11 12 import org.chromium.base.SysUtils; 13 import org.chromium.base.ThreadUtils; 14 15 /** 16 * Manages oom bindings used to bound child services. 17 */ 18 class BindingManagerImpl implements BindingManager { 19 private static final String TAG = "BindingManager"; 20 21 // Delay of 1 second used when removing the initial oom binding of a process. 22 private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000; 23 24 // Delay of 1 second used when removing temporary strong binding of a process (only on 25 // non-low-memory devices). 26 private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 1 * 1000; 27 28 // These fields allow to override the parameters for testing - see 29 // createBindingManagerForTesting(). 30 private final long mRemoveInitialBindingDelay; 31 private final long mRemoveStrongBindingDelay; 32 private final boolean mIsLowMemoryDevice; 33 34 /** 35 * Wraps ChildProcessConnection keeping track of additional information needed to manage the 36 * bindings of the connection. The reference to ChildProcessConnection is cleared when the 37 * connection goes away, but ManagedConnection itself is kept (until overwritten by a new entry 38 * for the same pid). 39 */ 40 private class ManagedConnection { 41 // Set in constructor, cleared in clearConnection(). 42 private ChildProcessConnection mConnection; 43 44 // True iff there is a strong binding kept on the service because it is working in 45 // foreground. 46 private boolean mInForeground; 47 48 // True iff there is a strong binding kept on the service because it was bound for the 49 // application background period. 50 private boolean mBoundForBackgroundPeriod; 51 52 // When mConnection is cleared, oom binding status is stashed here. 53 private boolean mWasOomProtected; 54 55 /** Removes the initial service binding. */ removeInitialBinding()56 private void removeInitialBinding() { 57 final ChildProcessConnection connection = mConnection; 58 if (connection == null || !connection.isInitialBindingBound()) return; 59 60 ThreadUtils.postOnUiThreadDelayed(new Runnable() { 61 @Override 62 public void run() { 63 if (connection.isInitialBindingBound()) { 64 connection.removeInitialBinding(); 65 } 66 } 67 }, mRemoveInitialBindingDelay); 68 } 69 70 /** Adds a strong service binding. */ addStrongBinding()71 private void addStrongBinding() { 72 ChildProcessConnection connection = mConnection; 73 if (connection == null) return; 74 75 connection.addStrongBinding(); 76 } 77 78 /** Removes a strong service binding. */ removeStrongBinding()79 private void removeStrongBinding() { 80 final ChildProcessConnection connection = mConnection; 81 // We have to fail gracefully if the strong binding is not present, as on low-end the 82 // binding could have been removed by dropOomBindings() when a new service was started. 83 if (connection == null || !connection.isStrongBindingBound()) return; 84 85 // This runnable performs the actual unbinding. It will be executed synchronously when 86 // on low-end devices and posted with a delay otherwise. 87 Runnable doUnbind = new Runnable() { 88 @Override 89 public void run() { 90 if (connection.isStrongBindingBound()) { 91 connection.removeStrongBinding(); 92 } 93 } 94 }; 95 96 if (mIsLowMemoryDevice) { 97 doUnbind.run(); 98 } else { 99 ThreadUtils.postOnUiThreadDelayed(doUnbind, mRemoveStrongBindingDelay); 100 } 101 } 102 103 /** 104 * Drops the service bindings. This is used on low-end to drop bindings of the current 105 * service when a new one is created. 106 */ dropBindings()107 private void dropBindings() { 108 assert mIsLowMemoryDevice; 109 ChildProcessConnection connection = mConnection; 110 if (connection == null) return; 111 112 connection.dropOomBindings(); 113 } 114 ManagedConnection(ChildProcessConnection connection)115 ManagedConnection(ChildProcessConnection connection) { 116 mConnection = connection; 117 } 118 119 /** 120 * Sets the visibility of the service, adding or removing the strong binding as needed. This 121 * also removes the initial binding, as the service visibility is now known. 122 */ setInForeground(boolean nextInForeground)123 void setInForeground(boolean nextInForeground) { 124 if (!mInForeground && nextInForeground) { 125 addStrongBinding(); 126 } else if (mInForeground && !nextInForeground) { 127 removeStrongBinding(); 128 } 129 130 removeInitialBinding(); 131 mInForeground = nextInForeground; 132 } 133 134 /** 135 * Sets or removes additional binding when the service is main service during the embedder 136 * background period. 137 */ setBoundForBackgroundPeriod(boolean nextBound)138 void setBoundForBackgroundPeriod(boolean nextBound) { 139 if (!mBoundForBackgroundPeriod && nextBound) { 140 addStrongBinding(); 141 } else if (mBoundForBackgroundPeriod && !nextBound) { 142 removeStrongBinding(); 143 } 144 145 mBoundForBackgroundPeriod = nextBound; 146 } 147 isOomProtected()148 boolean isOomProtected() { 149 // When a process crashes, we can be queried about its oom status before or after the 150 // connection is cleared. For the latter case, the oom status is stashed in 151 // mWasOomProtected. 152 return mConnection != null ? 153 mConnection.isOomProtectedOrWasWhenDied() : mWasOomProtected; 154 } 155 clearConnection()156 void clearConnection() { 157 mWasOomProtected = mConnection.isOomProtectedOrWasWhenDied(); 158 mConnection = null; 159 } 160 161 /** @return true iff the reference to the connection is no longer held */ 162 @VisibleForTesting isConnectionCleared()163 boolean isConnectionCleared() { 164 return mConnection == null; 165 } 166 } 167 168 // This can be manipulated on different threads, synchronize access on mManagedConnections. 169 private final SparseArray<ManagedConnection> mManagedConnections = 170 new SparseArray<ManagedConnection>(); 171 172 // The connection that was most recently set as foreground (using setInForeground()). This is 173 // used to add additional binding on it when the embedder goes to background. On low-end, this 174 // is also used to drop process bidnings when a new one is created, making sure that only one 175 // renderer process at a time is protected from oom killing. 176 private ManagedConnection mLastInForeground; 177 178 // Synchronizes operations that access mLastInForeground: setInForeground() and 179 // addNewConnection(). 180 private final Object mLastInForegroundLock = new Object(); 181 182 // The connection bound with additional binding in onSentToBackground(). 183 private ManagedConnection mBoundForBackgroundPeriod; 184 185 /** 186 * The constructor is private to hide parameters exposed for testing from the regular consumer. 187 * Use factory methods to create an instance. 188 */ BindingManagerImpl(boolean isLowMemoryDevice, long removeInitialBindingDelay, long removeStrongBindingDelay)189 private BindingManagerImpl(boolean isLowMemoryDevice, long removeInitialBindingDelay, 190 long removeStrongBindingDelay) { 191 mIsLowMemoryDevice = isLowMemoryDevice; 192 mRemoveInitialBindingDelay = removeInitialBindingDelay; 193 mRemoveStrongBindingDelay = removeStrongBindingDelay; 194 } 195 createBindingManager()196 public static BindingManagerImpl createBindingManager() { 197 return new BindingManagerImpl(SysUtils.isLowEndDevice(), 198 REMOVE_INITIAL_BINDING_DELAY_MILLIS, DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS); 199 } 200 201 /** 202 * Creates a testing instance of BindingManager. Testing instance will have the unbinding delays 203 * set to 0, so that the tests don't need to deal with actual waiting. 204 * @param isLowEndDevice true iff the created instance should apply low-end binding policies 205 */ createBindingManagerForTesting(boolean isLowEndDevice)206 public static BindingManagerImpl createBindingManagerForTesting(boolean isLowEndDevice) { 207 return new BindingManagerImpl(isLowEndDevice, 0, 0); 208 } 209 210 @Override addNewConnection(int pid, ChildProcessConnection connection)211 public void addNewConnection(int pid, ChildProcessConnection connection) { 212 synchronized (mLastInForegroundLock) { 213 if (mIsLowMemoryDevice && mLastInForeground != null) mLastInForeground.dropBindings(); 214 } 215 216 // This will reset the previous entry for the pid in the unlikely event of the OS 217 // reusing renderer pids. 218 synchronized (mManagedConnections) { 219 mManagedConnections.put(pid, new ManagedConnection(connection)); 220 } 221 } 222 223 @Override setInForeground(int pid, boolean inForeground)224 public void setInForeground(int pid, boolean inForeground) { 225 ManagedConnection managedConnection; 226 synchronized (mManagedConnections) { 227 managedConnection = mManagedConnections.get(pid); 228 } 229 230 if (managedConnection == null) { 231 Log.w(TAG, "Cannot setInForeground() - never saw a connection for the pid: " + 232 Integer.toString(pid)); 233 return; 234 } 235 236 synchronized (mLastInForegroundLock) { 237 managedConnection.setInForeground(inForeground); 238 if (inForeground) mLastInForeground = managedConnection; 239 } 240 } 241 242 @Override onSentToBackground()243 public void onSentToBackground() { 244 assert mBoundForBackgroundPeriod == null; 245 synchronized (mLastInForegroundLock) { 246 // mLastInForeground can be null at this point as the embedding application could be 247 // used in foreground without spawning any renderers. 248 if (mLastInForeground != null) { 249 mLastInForeground.setBoundForBackgroundPeriod(true); 250 mBoundForBackgroundPeriod = mLastInForeground; 251 } 252 } 253 } 254 255 @Override onBroughtToForeground()256 public void onBroughtToForeground() { 257 if (mBoundForBackgroundPeriod != null) { 258 mBoundForBackgroundPeriod.setBoundForBackgroundPeriod(false); 259 mBoundForBackgroundPeriod = null; 260 } 261 } 262 263 @Override isOomProtected(int pid)264 public boolean isOomProtected(int pid) { 265 // In the unlikely event of the OS reusing renderer pid, the call will refer to the most 266 // recent renderer of the given pid. The binding state for a pid is being reset in 267 // addNewConnection(). 268 ManagedConnection managedConnection; 269 synchronized (mManagedConnections) { 270 managedConnection = mManagedConnections.get(pid); 271 } 272 return managedConnection != null ? managedConnection.isOomProtected() : false; 273 } 274 275 @Override clearConnection(int pid)276 public void clearConnection(int pid) { 277 ManagedConnection managedConnection; 278 synchronized (mManagedConnections) { 279 managedConnection = mManagedConnections.get(pid); 280 } 281 if (managedConnection != null) managedConnection.clearConnection(); 282 } 283 284 /** @return true iff the connection reference is no longer held */ 285 @VisibleForTesting isConnectionCleared(int pid)286 public boolean isConnectionCleared(int pid) { 287 synchronized (mManagedConnections) { 288 return mManagedConnections.get(pid).isConnectionCleared(); 289 } 290 } 291 } 292