• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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