• Home
  • 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 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