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.
5 package org.chromium.content.browser;
7 import android.util.Log;
8 import android.util.SparseArray;
10 import org.chromium.base.SysUtils;
11 import org.chromium.base.ThreadUtils;
12 import org.chromium.base.VisibleForTesting;
15 * Manages oom bindings used to bound child services.
17 class BindingManagerImpl implements BindingManager {
18 private static final String TAG = "BindingManager";
20 // Delay of 1 second used when removing temporary strong binding of a process (only on
21 // non-low-memory devices).
22 private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 1 * 1000;
24 // These fields allow to override the parameters for testing - see
25 // createBindingManagerForTesting().
26 private final long mRemoveStrongBindingDelay;
27 private final boolean mIsLowMemoryDevice;
30 * Wraps ChildProcessConnection keeping track of additional information needed to manage the
31 * bindings of the connection. The reference to ChildProcessConnection is cleared when the
32 * connection goes away, but ManagedConnection itself is kept (until overwritten by a new entry
35 private class ManagedConnection {
36 // Set in constructor, cleared in clearConnection().
37 private ChildProcessConnection mConnection;
39 // True iff there is a strong binding kept on the service because it is working in
41 private boolean mInForeground;
43 // True iff there is a strong binding kept on the service because it was bound for the
44 // application background period.
45 private boolean mBoundForBackgroundPeriod;
47 // When mConnection is cleared, oom binding status is stashed here.
48 private boolean mWasOomProtected;
50 /** Removes the initial service binding. */
51 private void removeInitialBinding() {
52 if (mConnection == null || !mConnection.isInitialBindingBound()) return;
53 mConnection.removeInitialBinding();
56 /** Adds a strong service binding. */
57 private void addStrongBinding() {
58 ChildProcessConnection connection = mConnection;
59 if (connection == null) return;
61 connection.addStrongBinding();
64 /** Removes a strong service binding. */
65 private void removeStrongBinding() {
66 final ChildProcessConnection connection = mConnection;
67 // We have to fail gracefully if the strong binding is not present, as on low-end the
68 // binding could have been removed by dropOomBindings() when a new service was started.
69 if (connection == null || !connection.isStrongBindingBound()) return;
71 // This runnable performs the actual unbinding. It will be executed synchronously when
72 // on low-end devices and posted with a delay otherwise.
73 Runnable doUnbind = new Runnable() {
76 if (connection.isStrongBindingBound()) {
77 connection.removeStrongBinding();
82 if (mIsLowMemoryDevice) {
85 ThreadUtils.postOnUiThreadDelayed(doUnbind, mRemoveStrongBindingDelay);
90 * Drops the service bindings. This is used on low-end to drop bindings of the current
91 * service when a new one is created.
93 private void dropBindings() {
94 assert mIsLowMemoryDevice;
95 ChildProcessConnection connection = mConnection;
96 if (connection == null) return;
98 connection.dropOomBindings();
101 ManagedConnection(ChildProcessConnection connection) {
102 mConnection = connection;
106 * Sets the visibility of the service, adding or removing the strong binding as needed.
108 void setInForeground(boolean nextInForeground) {
109 if (!mInForeground && nextInForeground) {
111 } else if (mInForeground && !nextInForeground) {
112 removeStrongBinding();
115 mInForeground = nextInForeground;
119 * Removes the initial binding.
121 void determinedVisibility() {
122 removeInitialBinding();
126 * Sets or removes additional binding when the service is main service during the embedder
129 void setBoundForBackgroundPeriod(boolean nextBound) {
130 if (!mBoundForBackgroundPeriod && nextBound) {
132 } else if (mBoundForBackgroundPeriod && !nextBound) {
133 removeStrongBinding();
136 mBoundForBackgroundPeriod = nextBound;
139 boolean isOomProtected() {
140 // When a process crashes, we can be queried about its oom status before or after the
141 // connection is cleared. For the latter case, the oom status is stashed in
143 return mConnection != null
144 ? mConnection.isOomProtectedOrWasWhenDied() : mWasOomProtected;
147 void clearConnection() {
148 mWasOomProtected = mConnection.isOomProtectedOrWasWhenDied();
152 /** @return true iff the reference to the connection is no longer held */
154 boolean isConnectionCleared() {
155 return mConnection == null;
159 // This can be manipulated on different threads, synchronize access on mManagedConnections.
160 private final SparseArray<ManagedConnection> mManagedConnections =
161 new SparseArray<ManagedConnection>();
163 // The connection that was most recently set as foreground (using setInForeground()). This is
164 // used to add additional binding on it when the embedder goes to background. On low-end, this
165 // is also used to drop process bidnings when a new one is created, making sure that only one
166 // renderer process at a time is protected from oom killing.
167 private ManagedConnection mLastInForeground;
169 // Synchronizes operations that access mLastInForeground: setInForeground() and
170 // addNewConnection().
171 private final Object mLastInForegroundLock = new Object();
173 // The connection bound with additional binding in onSentToBackground().
174 private ManagedConnection mBoundForBackgroundPeriod;
177 * The constructor is private to hide parameters exposed for testing from the regular consumer.
178 * Use factory methods to create an instance.
180 private BindingManagerImpl(boolean isLowMemoryDevice, long removeStrongBindingDelay) {
181 mIsLowMemoryDevice = isLowMemoryDevice;
182 mRemoveStrongBindingDelay = removeStrongBindingDelay;
185 public static BindingManagerImpl createBindingManager() {
186 return new BindingManagerImpl(SysUtils.isLowEndDevice(),
187 DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS);
191 * Creates a testing instance of BindingManager. Testing instance will have the unbinding delays
192 * set to 0, so that the tests don't need to deal with actual waiting.
193 * @param isLowEndDevice true iff the created instance should apply low-end binding policies
195 public static BindingManagerImpl createBindingManagerForTesting(boolean isLowEndDevice) {
196 return new BindingManagerImpl(isLowEndDevice, 0);
200 public void addNewConnection(int pid, ChildProcessConnection connection) {
201 synchronized (mLastInForegroundLock) {
202 if (mIsLowMemoryDevice && mLastInForeground != null) mLastInForeground.dropBindings();
205 // This will reset the previous entry for the pid in the unlikely event of the OS
206 // reusing renderer pids.
207 synchronized (mManagedConnections) {
208 mManagedConnections.put(pid, new ManagedConnection(connection));
213 public void setInForeground(int pid, boolean inForeground) {
214 ManagedConnection managedConnection;
215 synchronized (mManagedConnections) {
216 managedConnection = mManagedConnections.get(pid);
219 if (managedConnection == null) {
220 Log.w(TAG, "Cannot setInForeground() - never saw a connection for the pid: "
221 + Integer.toString(pid));
225 synchronized (mLastInForegroundLock) {
226 managedConnection.setInForeground(inForeground);
227 if (inForeground) mLastInForeground = managedConnection;
232 public void determinedVisibility(int pid) {
233 ManagedConnection managedConnection;
234 synchronized (mManagedConnections) {
235 managedConnection = mManagedConnections.get(pid);
238 if (managedConnection == null) {
239 Log.w(TAG, "Cannot call determinedVisibility() - never saw a connection for the pid: "
240 + Integer.toString(pid));
244 managedConnection.determinedVisibility();
248 public void onSentToBackground() {
249 assert mBoundForBackgroundPeriod == null;
250 synchronized (mLastInForegroundLock) {
251 // mLastInForeground can be null at this point as the embedding application could be
252 // used in foreground without spawning any renderers.
253 if (mLastInForeground != null) {
254 mLastInForeground.setBoundForBackgroundPeriod(true);
255 mBoundForBackgroundPeriod = mLastInForeground;
261 public void onBroughtToForeground() {
262 if (mBoundForBackgroundPeriod != null) {
263 mBoundForBackgroundPeriod.setBoundForBackgroundPeriod(false);
264 mBoundForBackgroundPeriod = null;
269 public boolean isOomProtected(int pid) {
270 // In the unlikely event of the OS reusing renderer pid, the call will refer to the most
271 // recent renderer of the given pid. The binding state for a pid is being reset in
272 // addNewConnection().
273 ManagedConnection managedConnection;
274 synchronized (mManagedConnections) {
275 managedConnection = mManagedConnections.get(pid);
277 return managedConnection != null ? managedConnection.isOomProtected() : false;
281 public void clearConnection(int pid) {
282 ManagedConnection managedConnection;
283 synchronized (mManagedConnections) {
284 managedConnection = mManagedConnections.get(pid);
286 if (managedConnection != null) managedConnection.clearConnection();
289 /** @return true iff the connection reference is no longer held */
291 public boolean isConnectionCleared(int pid) {
292 synchronized (mManagedConnections) {
293 return mManagedConnections.get(pid).isConnectionCleared();