Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(172)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/BindingManagerImpl.java

Issue 2845243002: Moving BindingManager and ChildProcessConnection to base/.
Patch Set: Moving BindingManager and ChildProcessConnection to base/. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.annotation.TargetApi;
8 import android.content.ComponentCallbacks2;
9 import android.content.Context;
10 import android.content.res.Configuration;
11 import android.os.Build;
12 import android.util.LruCache;
13 import android.util.SparseArray;
14
15 import org.chromium.base.Log;
16 import org.chromium.base.SysUtils;
17 import org.chromium.base.ThreadUtils;
18 import org.chromium.base.VisibleForTesting;
19 import org.chromium.base.metrics.RecordHistogram;
20
21 import java.util.Map;
22
23 /**
24 * Manages oom bindings used to bound child services.
25 * This object must only be accessed from the launcher thread.
26 */
27 class BindingManagerImpl implements BindingManager {
28 private static final String TAG = "cr.BindingManager";
29
30 // Low reduce ratio of moderate binding.
31 private static final float MODERATE_BINDING_LOW_REDUCE_RATIO = 0.25f;
32 // High reduce ratio of moderate binding.
33 private static final float MODERATE_BINDING_HIGH_REDUCE_RATIO = 0.5f;
34
35 // Delay of 1 second used when removing temporary strong binding of a proces s (only on
36 // non-low-memory devices).
37 private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 1 * 1000;
38
39 // Delays used when clearing moderate binding pool when onSentToBackground h appens.
40 private static final long MODERATE_BINDING_POOL_CLEARER_DELAY_MILLIS = 10 * 1000;
41
42 // These fields allow to override the parameters for testing - see
43 // createBindingManagerForTesting().
44 private final boolean mIsLowMemoryDevice;
45
46 private static class ModerateBindingPool
47 extends LruCache<Integer, ManagedConnection> implements ComponentCal lbacks2 {
48 private Runnable mDelayedClearer;
49
50 public ModerateBindingPool(int maxSize) {
51 super(maxSize);
52 }
53
54 @Override
55 public void onTrimMemory(final int level) {
56 ThreadUtils.assertOnUiThread();
57 LauncherThread.post(new Runnable() {
58 @Override
59 public void run() {
60 Log.i(TAG, "onTrimMemory: level=%d, size=%d", level, size()) ;
61 if (size() <= 0) {
62 return;
63 }
64 if (level <= TRIM_MEMORY_RUNNING_MODERATE) {
65 reduce(MODERATE_BINDING_LOW_REDUCE_RATIO);
66 } else if (level <= TRIM_MEMORY_RUNNING_LOW) {
67 reduce(MODERATE_BINDING_HIGH_REDUCE_RATIO);
68 } else if (level == TRIM_MEMORY_UI_HIDDEN) {
69 // This will be handled by |mDelayedClearer|.
70 return;
71 } else {
72 evictAll();
73 }
74 }
75 });
76 }
77
78 @Override
79 public void onLowMemory() {
80 ThreadUtils.assertOnUiThread();
81 LauncherThread.post(new Runnable() {
82 @Override
83 public void run() {
84 Log.i(TAG, "onLowMemory: evict %d bindings", size());
85 evictAll();
86 }
87 });
88 }
89
90 @Override
91 public void onConfigurationChanged(Configuration configuration) {}
92
93 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
94 private void reduce(float reduceRatio) {
95 int oldSize = size();
96 int newSize = (int) (oldSize * (1f - reduceRatio));
97 Log.i(TAG, "Reduce connections from %d to %d", oldSize, newSize);
98 if (newSize == 0) {
99 evictAll();
100 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_M R1) {
101 trimToSize(newSize);
102 } else {
103 // Entries will be removed from the front because snapshot() ret urns ones ordered
104 // from least recently accessed to most recently accessed.
105 int count = 0;
106 for (Map.Entry<Integer, ManagedConnection> entry : snapshot().en trySet()) {
107 remove(entry.getKey());
108 ++count;
109 if (count == oldSize - newSize) break;
110 }
111 }
112 }
113
114 void addConnection(ManagedConnection managedConnection) {
115 ManagedChildProcessConnection connection = managedConnection.mConnec tion;
116 if (connection != null && connection.isSandboxed()) {
117 managedConnection.addModerateBinding();
118 if (connection.isModerateBindingBound()) {
119 put(connection.getServiceNumber(), managedConnection);
120 } else {
121 remove(connection.getServiceNumber());
122 }
123 }
124 }
125
126 void removeConnection(ManagedConnection managedConnection) {
127 ManagedChildProcessConnection connection = managedConnection.mConnec tion;
128 if (connection != null && connection.isSandboxed()) {
129 remove(connection.getServiceNumber());
130 }
131 }
132
133 @Override
134 protected void entryRemoved(boolean evicted, Integer key, ManagedConnect ion oldValue,
135 ManagedConnection newValue) {
136 if (oldValue != newValue) {
137 oldValue.removeModerateBinding();
138 }
139 }
140
141 void onSentToBackground(final boolean onTesting) {
142 if (size() == 0) return;
143 mDelayedClearer = new Runnable() {
144 @Override
145 public void run() {
146 if (mDelayedClearer == null) return;
147 mDelayedClearer = null;
148 Log.i(TAG, "Release moderate connections: %d", size());
149 if (!onTesting) {
150 RecordHistogram.recordCountHistogram(
151 "Android.ModerateBindingCount", size());
152 }
153 evictAll();
154 }
155 };
156 LauncherThread.postDelayed(mDelayedClearer, MODERATE_BINDING_POOL_CL EARER_DELAY_MILLIS);
157 }
158
159 void onBroughtToForeground() {
160 if (mDelayedClearer != null) {
161 LauncherThread.removeCallbacks(mDelayedClearer);
162 mDelayedClearer = null;
163 }
164 }
165 }
166
167 private ModerateBindingPool mModerateBindingPool;
168
169 /**
170 * Wraps ManagedChildProcessConnection keeping track of additional informati on needed to manage
171 * the bindings of the connection. The reference to ManagedChildProcessConne ction is cleared
172 * when the connection goes away, but ManagedConnection itself is kept (unti l overwritten by a
173 * new entry for the same pid).
174 */
175 private class ManagedConnection {
176 // Set in constructor, cleared in clearConnection() (on a separate threa d).
177 // Need to keep a local reference to avoid it being cleared while using it.
178 private ManagedChildProcessConnection mConnection;
179
180 // True iff there is a strong binding kept on the service because it is working in
181 // foreground.
182 private boolean mInForeground;
183
184 // True iff there is a strong binding kept on the service because it was bound for the
185 // application background period.
186 private boolean mBoundForBackgroundPeriod;
187
188 /**
189 * Removes the initial service binding.
190 * @return true if the binding was removed.
191 */
192 private boolean removeInitialBinding() {
193 ManagedChildProcessConnection connection = mConnection;
194 if (connection == null || !connection.isInitialBindingBound()) retur n false;
195
196 connection.removeInitialBinding();
197 return true;
198 }
199
200 /** Adds a strong service binding. */
201 private void addStrongBinding() {
202 ManagedChildProcessConnection connection = mConnection;
203 if (connection == null) return;
204
205 connection.addStrongBinding();
206 if (mModerateBindingPool != null) mModerateBindingPool.removeConnect ion(this);
207 }
208
209 /** Removes a strong service binding. */
210 private void removeStrongBinding(final boolean keepAsModerate) {
211 final ManagedChildProcessConnection connection = mConnection;
212 // We have to fail gracefully if the strong binding is not present, as on low-end the
213 // binding could have been removed by dropOomBindings() when a new s ervice was started.
214 if (connection == null || !connection.isStrongBindingBound()) return ;
215
216 // This runnable performs the actual unbinding. It will be executed synchronously when
217 // on low-end devices and posted with a delay otherwise.
218 Runnable doUnbind = new Runnable() {
219 @Override
220 public void run() {
221 if (connection.isStrongBindingBound()) {
222 connection.removeStrongBinding();
223 if (keepAsModerate) {
224 addConnectionToModerateBindingPool(connection);
225 }
226 }
227 }
228 };
229
230 if (mIsLowMemoryDevice) {
231 doUnbind.run();
232 } else {
233 LauncherThread.postDelayed(doUnbind, DETACH_AS_ACTIVE_HIGH_END_D ELAY_MILLIS);
234 }
235 }
236
237 /**
238 * Adds connection to the moderate binding pool. No-op if the connection has a strong
239 * binding.
240 * @param connection The ChildProcessConnection to add to the moderate b inding pool.
241 */
242 private void addConnectionToModerateBindingPool(ManagedChildProcessConne ction connection) {
243 if (mModerateBindingPool != null && !connection.isStrongBindingBound ()) {
244 mModerateBindingPool.addConnection(ManagedConnection.this);
245 }
246 }
247
248 /** Removes the moderate service binding. */
249 private void removeModerateBinding() {
250 ManagedChildProcessConnection connection = mConnection;
251 if (connection == null || !connection.isModerateBindingBound()) retu rn;
252 connection.removeModerateBinding();
253 }
254
255 /** Adds the moderate service binding. */
256 private void addModerateBinding() {
257 ManagedChildProcessConnection connection = mConnection;
258 if (connection == null) return;
259
260 connection.addModerateBinding();
261 }
262
263 /**
264 * Drops the service bindings. This is used on low-end to drop bindings of the current
265 * service when a new one is used in foreground.
266 */
267 private void dropBindings() {
268 assert mIsLowMemoryDevice;
269 ManagedChildProcessConnection connection = mConnection;
270 if (connection == null) return;
271
272 connection.dropOomBindings();
273 }
274
275 ManagedConnection(ManagedChildProcessConnection connection) {
276 mConnection = connection;
277 }
278
279 /**
280 * Sets the visibility of the service, adding or removing the strong bin ding as needed.
281 */
282 void setInForeground(boolean nextInForeground) {
283 if (!mInForeground && nextInForeground) {
284 addStrongBinding();
285 } else if (mInForeground && !nextInForeground) {
286 removeStrongBinding(true);
287 }
288
289 mInForeground = nextInForeground;
290 }
291
292 /**
293 * Called when it is safe to rely on setInForeground() for binding manag ement.
294 */
295 void onDeterminedVisibility() {
296 if (!removeInitialBinding()) return;
297 // Decrease the likelihood of a recently created background tab gett ing evicted by
298 // immediately adding moderate binding.
299 addConnectionToModerateBindingPool(mConnection);
300 }
301
302 /**
303 * Sets or removes additional binding when the service is main service d uring the embedder
304 * background period.
305 */
306 void setBoundForBackgroundPeriod(boolean nextBound) {
307 if (!mBoundForBackgroundPeriod && nextBound) {
308 addStrongBinding();
309 } else if (mBoundForBackgroundPeriod && !nextBound) {
310 removeStrongBinding(false);
311 }
312
313 mBoundForBackgroundPeriod = nextBound;
314 }
315
316 void clearConnection() {
317 if (mModerateBindingPool != null) mModerateBindingPool.removeConnect ion(this);
318 mConnection = null;
319 }
320 }
321
322 private final SparseArray<ManagedConnection> mManagedConnections =
323 new SparseArray<ManagedConnection>();
324
325 // The connection that was most recently set as foreground (using setInForeg round()). This is
326 // used to add additional binding on it when the embedder goes to background . On low-end, this
327 // is also used to drop process bindings when a new one is created, making s ure that only one
328 // renderer process at a time is protected from oom killing.
329 private ManagedConnection mLastInForeground;
330
331 // The connection bound with additional binding in onSentToBackground().
332 private ManagedConnection mBoundForBackgroundPeriod;
333
334 // Whether this instance is used on testing.
335 private final boolean mOnTesting;
336
337 /**
338 * The constructor is private to hide parameters exposed for testing from th e regular consumer.
339 * Use factory methods to create an instance.
340 */
341 private BindingManagerImpl(boolean isLowMemoryDevice, boolean onTesting) {
342 assert LauncherThread.runningOnLauncherThread();
343 mIsLowMemoryDevice = isLowMemoryDevice;
344 mOnTesting = onTesting;
345 }
346
347 public static BindingManagerImpl createBindingManager() {
348 assert LauncherThread.runningOnLauncherThread();
349 return new BindingManagerImpl(SysUtils.isLowEndDevice(), false);
350 }
351
352 /**
353 * Creates a testing instance of BindingManager. Testing instance will have the unbinding delays
354 * set to 0, so that the tests don't need to deal with actual waiting.
355 * @param isLowEndDevice true iff the created instance should apply low-end binding policies
356 */
357 public static BindingManagerImpl createBindingManagerForTesting(boolean isLo wEndDevice) {
358 assert LauncherThread.runningOnLauncherThread();
359 return new BindingManagerImpl(isLowEndDevice, true);
360 }
361
362 @Override
363 public void addNewConnection(int pid, ManagedChildProcessConnection connecti on) {
364 assert LauncherThread.runningOnLauncherThread();
365 // This will reset the previous entry for the pid in the unlikely event of the OS
366 // reusing renderer pids.
367 mManagedConnections.put(pid, new ManagedConnection(connection));
368 }
369
370 @Override
371 public void setInForeground(int pid, boolean inForeground) {
372 assert LauncherThread.runningOnLauncherThread();
373 ManagedConnection managedConnection = mManagedConnections.get(pid);
374 if (managedConnection == null) {
375 Log.w(TAG, "Cannot setInForeground() - never saw a connection for th e pid: %d", pid);
376 return;
377 }
378
379 if (inForeground && mIsLowMemoryDevice && mLastInForeground != null
380 && mLastInForeground != managedConnection) {
381 mLastInForeground.dropBindings();
382 }
383
384 managedConnection.setInForeground(inForeground);
385 if (inForeground) mLastInForeground = managedConnection;
386 }
387
388 @Override
389 public void onDeterminedVisibility(int pid) {
390 assert LauncherThread.runningOnLauncherThread();
391 ManagedConnection managedConnection = mManagedConnections.get(pid);
392 if (managedConnection == null) {
393 Log.w(TAG, "Cannot call determinedVisibility() - never saw a connect ion for the pid: "
394 + "%d", pid);
395 return;
396 }
397
398 managedConnection.onDeterminedVisibility();
399 }
400
401 @Override
402 public void onSentToBackground() {
403 assert LauncherThread.runningOnLauncherThread();
404 assert mBoundForBackgroundPeriod == null;
405 // mLastInForeground can be null at this point as the embedding applicat ion could be
406 // used in foreground without spawning any renderers.
407 if (mLastInForeground != null) {
408 mLastInForeground.setBoundForBackgroundPeriod(true);
409 mBoundForBackgroundPeriod = mLastInForeground;
410 }
411 if (mModerateBindingPool != null) mModerateBindingPool.onSentToBackgroun d(mOnTesting);
412 }
413
414 @Override
415 public void onBroughtToForeground() {
416 assert LauncherThread.runningOnLauncherThread();
417 if (mBoundForBackgroundPeriod != null) {
418 mBoundForBackgroundPeriod.setBoundForBackgroundPeriod(false);
419 mBoundForBackgroundPeriod = null;
420 }
421 if (mModerateBindingPool != null) mModerateBindingPool.onBroughtToForegr ound();
422 }
423
424 @Override
425 public void removeConnection(int pid) {
426 assert LauncherThread.runningOnLauncherThread();
427 ManagedConnection managedConnection = mManagedConnections.get(pid);
428 if (managedConnection != null) {
429 mManagedConnections.remove(pid);
430 managedConnection.clearConnection();
431 }
432 }
433
434 /** @return true iff the connection reference is no longer held */
435 @VisibleForTesting
436 public boolean isConnectionCleared(int pid) {
437 assert LauncherThread.runningOnLauncherThread();
438 return mManagedConnections.get(pid) == null;
439 }
440
441 @Override
442 public void startModerateBindingManagement(Context context, int maxSize) {
443 assert LauncherThread.runningOnLauncherThread();
444 if (mIsLowMemoryDevice) return;
445
446 if (mModerateBindingPool == null) {
447 Log.i(TAG, "Moderate binding enabled: maxSize=%d", maxSize);
448 mModerateBindingPool = new ModerateBindingPool(maxSize);
449 if (context != null) {
450 // Note that it is safe to call Context.registerComponentCallbac ks from a background
451 // thread.
452 context.registerComponentCallbacks(mModerateBindingPool);
453 }
454 }
455 }
456
457 @Override
458 public void releaseAllModerateBindings() {
459 assert LauncherThread.runningOnLauncherThread();
460 if (mModerateBindingPool != null) {
461 Log.i(TAG, "Release all moderate bindings: %d", mModerateBindingPool .size());
462 mModerateBindingPool.evictAll();
463 }
464 }
465 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698