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