Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / BindingManagerImpl.java
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 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;
23
24     // These fields allow to override the parameters for testing - see
25     // createBindingManagerForTesting().
26     private final long mRemoveStrongBindingDelay;
27     private final boolean mIsLowMemoryDevice;
28
29     /**
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
33      * for the same pid).
34      */
35     private class ManagedConnection {
36         // Set in constructor, cleared in clearConnection().
37         private ChildProcessConnection mConnection;
38
39         // True iff there is a strong binding kept on the service because it is working in
40         // foreground.
41         private boolean mInForeground;
42
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;
46
47         // When mConnection is cleared, oom binding status is stashed here.
48         private boolean mWasOomProtected;
49
50         /** Removes the initial service binding. */
51         private void removeInitialBinding() {
52             if (mConnection == null || !mConnection.isInitialBindingBound()) return;
53             mConnection.removeInitialBinding();
54         }
55
56         /** Adds a strong service binding. */
57         private void addStrongBinding() {
58             ChildProcessConnection connection = mConnection;
59             if (connection == null) return;
60
61             connection.addStrongBinding();
62         }
63
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;
70
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() {
74                 @Override
75                 public void run() {
76                     if (connection.isStrongBindingBound()) {
77                         connection.removeStrongBinding();
78                     }
79                 }
80             };
81
82             if (mIsLowMemoryDevice) {
83                 doUnbind.run();
84             } else {
85                 ThreadUtils.postOnUiThreadDelayed(doUnbind, mRemoveStrongBindingDelay);
86             }
87         }
88
89         /**
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.
92          */
93         private void dropBindings() {
94             assert mIsLowMemoryDevice;
95             ChildProcessConnection connection = mConnection;
96             if (connection == null) return;
97
98             connection.dropOomBindings();
99         }
100
101         ManagedConnection(ChildProcessConnection connection) {
102             mConnection = connection;
103         }
104
105         /**
106          * Sets the visibility of the service, adding or removing the strong binding as needed.
107          */
108         void setInForeground(boolean nextInForeground) {
109             if (!mInForeground && nextInForeground) {
110                 addStrongBinding();
111             } else if (mInForeground && !nextInForeground) {
112                 removeStrongBinding();
113             }
114
115             mInForeground = nextInForeground;
116         }
117
118         /**
119          * Removes the initial binding.
120          */
121         void determinedVisibility() {
122             removeInitialBinding();
123         }
124
125         /**
126          * Sets or removes additional binding when the service is main service during the embedder
127          * background period.
128          */
129         void setBoundForBackgroundPeriod(boolean nextBound) {
130             if (!mBoundForBackgroundPeriod && nextBound) {
131                 addStrongBinding();
132             } else if (mBoundForBackgroundPeriod && !nextBound) {
133                 removeStrongBinding();
134             }
135
136             mBoundForBackgroundPeriod = nextBound;
137         }
138
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
142             // mWasOomProtected.
143             return mConnection != null
144                     ? mConnection.isOomProtectedOrWasWhenDied() : mWasOomProtected;
145         }
146
147         void clearConnection() {
148             mWasOomProtected = mConnection.isOomProtectedOrWasWhenDied();
149             mConnection = null;
150         }
151
152         /** @return true iff the reference to the connection is no longer held */
153         @VisibleForTesting
154         boolean isConnectionCleared() {
155             return mConnection == null;
156         }
157     }
158
159     // This can be manipulated on different threads, synchronize access on mManagedConnections.
160     private final SparseArray<ManagedConnection> mManagedConnections =
161             new SparseArray<ManagedConnection>();
162
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;
168
169     // Synchronizes operations that access mLastInForeground: setInForeground() and
170     // addNewConnection().
171     private final Object mLastInForegroundLock = new Object();
172
173     // The connection bound with additional binding in onSentToBackground().
174     private ManagedConnection mBoundForBackgroundPeriod;
175
176     /**
177      * The constructor is private to hide parameters exposed for testing from the regular consumer.
178      * Use factory methods to create an instance.
179      */
180     private BindingManagerImpl(boolean isLowMemoryDevice, long removeStrongBindingDelay) {
181         mIsLowMemoryDevice = isLowMemoryDevice;
182         mRemoveStrongBindingDelay = removeStrongBindingDelay;
183     }
184
185     public static BindingManagerImpl createBindingManager() {
186         return new BindingManagerImpl(SysUtils.isLowEndDevice(),
187                 DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS);
188     }
189
190     /**
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
194      */
195     public static BindingManagerImpl createBindingManagerForTesting(boolean isLowEndDevice) {
196         return new BindingManagerImpl(isLowEndDevice, 0);
197     }
198
199     @Override
200     public void addNewConnection(int pid, ChildProcessConnection connection) {
201         synchronized (mLastInForegroundLock) {
202             if (mIsLowMemoryDevice && mLastInForeground != null) mLastInForeground.dropBindings();
203         }
204
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));
209         }
210     }
211
212     @Override
213     public void setInForeground(int pid, boolean inForeground) {
214         ManagedConnection managedConnection;
215         synchronized (mManagedConnections) {
216             managedConnection = mManagedConnections.get(pid);
217         }
218
219         if (managedConnection == null) {
220             Log.w(TAG, "Cannot setInForeground() - never saw a connection for the pid: "
221                     + Integer.toString(pid));
222             return;
223         }
224
225         synchronized (mLastInForegroundLock) {
226             managedConnection.setInForeground(inForeground);
227             if (inForeground) mLastInForeground = managedConnection;
228         }
229     }
230
231     @Override
232     public void determinedVisibility(int pid) {
233         ManagedConnection managedConnection;
234         synchronized (mManagedConnections) {
235             managedConnection = mManagedConnections.get(pid);
236         }
237
238         if (managedConnection == null) {
239             Log.w(TAG, "Cannot call determinedVisibility() - never saw a connection for the pid: "
240                     + Integer.toString(pid));
241             return;
242         }
243
244         managedConnection.determinedVisibility();
245     }
246
247     @Override
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;
256             }
257         }
258     }
259
260     @Override
261     public void onBroughtToForeground() {
262         if (mBoundForBackgroundPeriod != null) {
263             mBoundForBackgroundPeriod.setBoundForBackgroundPeriod(false);
264             mBoundForBackgroundPeriod = null;
265         }
266     }
267
268     @Override
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);
276         }
277         return managedConnection != null ? managedConnection.isOomProtected() : false;
278     }
279
280     @Override
281     public void clearConnection(int pid) {
282         ManagedConnection managedConnection;
283         synchronized (mManagedConnections) {
284             managedConnection = mManagedConnections.get(pid);
285         }
286         if (managedConnection != null) managedConnection.clearConnection();
287     }
288
289     /** @return true iff the connection reference is no longer held */
290     @VisibleForTesting
291     public boolean isConnectionCleared(int pid) {
292         synchronized (mManagedConnections) {
293             return mManagedConnections.get(pid).isConnectionCleared();
294         }
295     }
296 }