OLD | NEW |
| (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 } | |
OLD | NEW |