BindingManagerImpl.java revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// found in the LICENSE file.
4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)package org.chromium.content.browser;
6a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import android.util.Log;
8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import android.util.SparseArray;
9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import org.chromium.base.SysUtils;
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import org.chromium.base.ThreadUtils;
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import org.chromium.base.VisibleForTesting;
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)/**
15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Manages oom bindings used to bound child services.
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class BindingManagerImpl implements BindingManager {
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private static final String TAG = "BindingManager";
19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Delay of 1 second used when removing the initial oom binding of a process.
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000;
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Delay of 1 second used when removing temporary strong binding of a process (only on
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // non-low-memory devices).
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 1 * 1000;
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
27a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // These fields allow to override the parameters for testing - see
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // createBindingManagerForTesting().
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private final long mRemoveInitialBindingDelay;
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private final long mRemoveStrongBindingDelay;
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private final boolean mIsLowMemoryDevice;
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /**
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * Wraps ChildProcessConnection keeping track of additional information needed to manage the
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * bindings of the connection. The reference to ChildProcessConnection is cleared when the
36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * connection goes away, but ManagedConnection itself is kept (until overwritten by a new entry
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * for the same pid).
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     */
39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    private class ManagedConnection {
40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // Set in constructor, cleared in clearConnection().
41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        private ChildProcessConnection mConnection;
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // True iff there is a strong binding kept on the service because it is working in
44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // foreground.
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        private boolean mInForeground;
46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // True iff there is a strong binding kept on the service because it was bound for the
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // 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. */
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. */
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. */
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         */
106        private void dropBindings() {
107            assert mIsLowMemoryDevice;
108            ChildProcessConnection connection = mConnection;
109            if (connection == null) return;
110
111            connection.dropOomBindings();
112        }
113
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         */
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         */
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
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
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
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     */
188    private BindingManagerImpl(boolean isLowMemoryDevice, long removeInitialBindingDelay,
189            long removeStrongBindingDelay) {
190        mIsLowMemoryDevice = isLowMemoryDevice;
191        mRemoveInitialBindingDelay = removeInitialBindingDelay;
192        mRemoveStrongBindingDelay = removeStrongBindingDelay;
193    }
194
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     */
205    public static BindingManagerImpl createBindingManagerForTesting(boolean isLowEndDevice) {
206        return new BindingManagerImpl(isLowEndDevice, 0, 0);
207    }
208
209    @Override
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
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
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
255    public void onBroughtToForeground() {
256        if (mBoundForBackgroundPeriod != null) {
257            mBoundForBackgroundPeriod.setBoundForBackgroundPeriod(false);
258            mBoundForBackgroundPeriod = null;
259        }
260    }
261
262    @Override
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
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
285    public boolean isConnectionCleared(int pid) {
286        synchronized (mManagedConnections) {
287            return mManagedConnections.get(pid).isConnectionCleared();
288        }
289    }
290}
291