ChildProcessConnection.java revision f2477e01787aa58f445919b809d89e252beef54f
1// Copyright 2012 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
5package org.chromium.content.browser;
6
7import android.content.ComponentName;
8import android.content.Context;
9import android.content.Intent;
10import android.content.ServiceConnection;
11import android.os.Bundle;
12import android.os.IBinder;
13import android.os.ParcelFileDescriptor;
14import android.util.Log;
15
16import org.chromium.base.CpuFeatures;
17import org.chromium.base.ThreadUtils;
18import org.chromium.content.app.ChildProcessService;
19import org.chromium.content.app.Linker;
20import org.chromium.content.app.LinkerParams;
21import org.chromium.content.common.IChildProcessCallback;
22import org.chromium.content.common.IChildProcessService;
23import org.chromium.content.common.TraceEvent;
24
25import java.io.IOException;
26
27/**
28 * Manages a connection between the browser activity and a child service. The class is responsible
29 * for estabilishing the connection (start()), closing it (stop()) and increasing the priority of
30 * the service when it is in active use (between calls to attachAsActive() and detachAsActive()).
31 */
32public class ChildProcessConnection {
33    /**
34     * Used to notify the consumer about disconnection of the service. This callback is provided
35     * earlier than ConnectionCallbacks below, as a child process might die before the connection is
36     * fully set up.
37     */
38    interface DeathCallback {
39        void onChildProcessDied(int pid);
40    }
41
42    /**
43     * Used to notify the consumer about the connection being established.
44     */
45    interface ConnectionCallback {
46        /**
47         * Called when the connection to the service is established.
48         * @param pid Pid of the child process.
49         */
50        void onConnected(int pid);
51    }
52
53    // Names of items placed in the bind intent or connection bundle.
54    public static final String EXTRA_COMMAND_LINE =
55            "com.google.android.apps.chrome.extra.command_line";
56    // Note the FDs may only be passed in the connection bundle.
57    public static final String EXTRA_FILES_PREFIX =
58            "com.google.android.apps.chrome.extra.extraFile_";
59    public static final String EXTRA_FILES_ID_SUFFIX = "_id";
60    public static final String EXTRA_FILES_FD_SUFFIX = "_fd";
61
62    // Used to pass the CPU core count to child processes.
63    public static final String EXTRA_CPU_COUNT =
64            "com.google.android.apps.chrome.extra.cpu_count";
65    // Used to pass the CPU features mask to child processes.
66    public static final String EXTRA_CPU_FEATURES =
67            "com.google.android.apps.chrome.extra.cpu_features";
68
69    private final Context mContext;
70    private final int mServiceNumber;
71    private final boolean mInSandbox;
72    private final ChildProcessConnection.DeathCallback mDeathCallback;
73    private final Class<? extends ChildProcessService> mServiceClass;
74
75    // Synchronization: While most internal flow occurs on the UI thread, the public API
76    // (specifically start and stop) may be called from any thread, hence all entry point methods
77    // into the class are synchronized on the lock to protect access to these members. But see also
78    // the TODO where AsyncBoundServiceConnection is created.
79    private final Object mLock = new Object();
80    private IChildProcessService mService = null;
81    // Set to true when the service connect is finished, even if it fails.
82    private boolean mServiceConnectComplete = false;
83    // Set to true when the service disconnects, as opposed to being properly closed. This happens
84    // when the process crashes or gets killed by the system out-of-memory killer.
85    private boolean mServiceDisconnected = false;
86    private int mPID = 0;  // Process ID of the corresponding child process.
87    // Initial binding protects the newly spawned process from being killed before it is put to use,
88    // it is maintained between calls to start() and removeInitialBinding().
89    private ChildServiceConnection mInitialBinding = null;
90    // Strong binding will make the service priority equal to the priority of the activity. We want
91    // the OS to be able to kill background renderers as it kills other background apps, so strong
92    // bindings are maintained only for services that are active at the moment (between
93    // attachAsActive() and detachAsActive()).
94    private ChildServiceConnection mStrongBinding = null;
95    // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
96    // to start() and stop().
97    private ChildServiceConnection mWaivedBinding = null;
98    // Incremented on attachAsActive(), decremented on detachAsActive().
99    private int mAttachAsActiveCount = 0;
100
101    // Linker-related parameters.
102    private LinkerParams mLinkerParams = null;
103
104    private static final String TAG = "ChildProcessConnection";
105
106    private static class ConnectionParams {
107        final String[] mCommandLine;
108        final FileDescriptorInfo[] mFilesToBeMapped;
109        final IChildProcessCallback mCallback;
110        final Bundle mSharedRelros;
111
112        ConnectionParams(String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
113                IChildProcessCallback callback, Bundle sharedRelros) {
114            mCommandLine = commandLine;
115            mFilesToBeMapped = filesToBeMapped;
116            mCallback = callback;
117            mSharedRelros = sharedRelros;
118        }
119    }
120
121    // This is set by the consumer of the class in setupConnection() and is later used in
122    // doSetupConnection(), after which the variable is cleared. Therefore this is only valid while
123    // the connection is being set up.
124    private ConnectionParams mConnectionParams;
125
126    // Callbacks used to notify the consumer about connection events. This is also provided in
127    // setupConnection(), but remains valid after setup.
128    private ChildProcessConnection.ConnectionCallback mConnectionCallback;
129
130    private class ChildServiceConnection implements ServiceConnection {
131        private boolean mBound = false;
132
133        private final int mBindFlags;
134
135        public ChildServiceConnection(int bindFlags) {
136            mBindFlags = bindFlags;
137        }
138
139        boolean bind(String[] commandLine) {
140            if (!mBound) {
141                final Intent intent = createServiceBindIntent();
142                if (commandLine != null) {
143                    intent.putExtra(EXTRA_COMMAND_LINE, commandLine);
144                }
145                if (mLinkerParams != null)
146                    mLinkerParams.addIntentExtras(intent);
147                mBound = mContext.bindService(intent, this, mBindFlags);
148            }
149            return mBound;
150        }
151
152        void unbind() {
153            if (mBound) {
154                mContext.unbindService(this);
155                mBound = false;
156            }
157        }
158
159        boolean isBound() {
160            return mBound;
161        }
162
163        @Override
164        public void onServiceConnected(ComponentName className, IBinder service) {
165            synchronized (mLock) {
166                // A flag from the parent class ensures we run the post-connection logic only once
167                // (instead of once per each ChildServiceConnection).
168                if (mServiceConnectComplete) {
169                    return;
170                }
171                TraceEvent.begin();
172                mServiceConnectComplete = true;
173                mService = IChildProcessService.Stub.asInterface(service);
174                // Make sure that the connection parameters have already been provided. If not,
175                // doConnectionSetup() will be called from setupConnection().
176                if (mConnectionParams != null) {
177                    doConnectionSetup();
178                }
179                TraceEvent.end();
180            }
181        }
182
183
184        // Called on the main thread to notify that the child service did not disconnect gracefully.
185        @Override
186        public void onServiceDisconnected(ComponentName className) {
187            // Ensure that the disconnection logic runs only once (instead of once per each
188            // ChildServiceConnection).
189            if (mServiceDisconnected) {
190                return;
191            }
192            mServiceDisconnected = true;
193            int pid = mPID;  // Stash the pid for DeathCallback since stop() will clear it.
194            boolean disconnectedWhileBeingSetUp = mConnectionParams != null;
195            Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=" + pid);
196            stop();  // We don't want to auto-restart on crash. Let the browser do that.
197            if (pid != 0) {
198                mDeathCallback.onChildProcessDied(pid);
199            }
200            // TODO(ppi): does anyone know why we need to do that?
201            if (disconnectedWhileBeingSetUp && mConnectionCallback != null) {
202                mConnectionCallback.onConnected(0);
203            }
204        }
205    }
206
207    ChildProcessConnection(Context context, int number, boolean inSandbox,
208            ChildProcessConnection.DeathCallback deathCallback,
209            Class<? extends ChildProcessService> serviceClass,
210            LinkerParams linkerParams) {
211        mContext = context;
212        mServiceNumber = number;
213        mInSandbox = inSandbox;
214        mDeathCallback = deathCallback;
215        mServiceClass = serviceClass;
216        mLinkerParams = linkerParams;
217        mInitialBinding = new ChildServiceConnection(Context.BIND_AUTO_CREATE);
218        mStrongBinding = new ChildServiceConnection(
219                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
220        mWaivedBinding = new ChildServiceConnection(
221                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
222    }
223
224    int getServiceNumber() {
225        return mServiceNumber;
226    }
227
228    boolean isInSandbox() {
229        return mInSandbox;
230    }
231
232    IChildProcessService getService() {
233        synchronized (mLock) {
234            return mService;
235        }
236    }
237
238    private Intent createServiceBindIntent() {
239        Intent intent = new Intent();
240        intent.setClassName(mContext, mServiceClass.getName() + mServiceNumber);
241        intent.setPackage(mContext.getPackageName());
242        return intent;
243    }
244
245    /**
246     * Starts a connection to an IChildProcessService. This must be followed by a call to
247     * setupConnection() to setup the connection parameters. start() and setupConnection() are
248     * separate to allow the client to pass whatever parameters they have available here, and
249     * complete the remainder later while reducing the connection setup latency.
250     * @param commandLine (Optional) Command line for the child process. If omitted, then
251     *                    the command line parameters must instead be passed to setupConnection().
252     */
253    void start(String[] commandLine) {
254        synchronized (mLock) {
255            TraceEvent.begin();
256            assert !ThreadUtils.runningOnUiThread();
257
258            if (!mInitialBinding.bind(commandLine)) {
259                onBindFailed();
260            } else {
261                mWaivedBinding.bind(null);
262            }
263            TraceEvent.end();
264        }
265    }
266
267    /**
268     * Setups the connection after it was started with start(). This method should be called by the
269     * consumer of the class to set up additional connection parameters.
270     * @param commandLine (Optional) will be ignored if the command line was already sent in bind()
271     * @param filesToBeMapped a list of file descriptors that should be registered
272     * @param processCallback Used for status updates regarding this process connection.
273     * @param connectionCallbacks will notify the consumer about the connection being established
274     * and the status of the out-of-memory bindings being bound for the connection.
275     */
276    void setupConnection(
277            String[] commandLine,
278            FileDescriptorInfo[] filesToBeMapped,
279            IChildProcessCallback processCallback,
280            ConnectionCallback connectionCallbacks,
281            Bundle sharedRelros) {
282        synchronized (mLock) {
283            TraceEvent.begin();
284            assert mConnectionParams == null;
285            mConnectionCallback = connectionCallbacks;
286            mConnectionParams = new ConnectionParams(
287                    commandLine, filesToBeMapped, processCallback, sharedRelros);
288            // Make sure that the service is already connected. If not, doConnectionSetup() will be
289            // called from onServiceConnected().
290            if (mServiceConnectComplete) {
291                doConnectionSetup();
292            }
293            TraceEvent.end();
294        }
295    }
296
297    /**
298     * Terminates the connection to IChildProcessService, closing all bindings. It is safe to call
299     * this multiple times.
300     */
301    void stop() {
302        synchronized (mLock) {
303            mInitialBinding.unbind();
304            mStrongBinding.unbind();
305            mWaivedBinding.unbind();
306            mAttachAsActiveCount = 0;
307            if (mService != null) {
308                mService = null;
309                mPID = 0;
310            }
311            mConnectionParams = null;
312            mServiceConnectComplete = false;
313        }
314    }
315
316    // Called on the main thread to notify that the bindService() call failed (returned false).
317    private void onBindFailed() {
318        mServiceConnectComplete = true;
319        if (mConnectionParams != null) {
320            doConnectionSetup();
321        }
322    }
323
324    /**
325     * Called after the connection parameters have been set (in setupConnection()) *and* a
326     * connection has been established (as signaled by onServiceConnected()) or failed (as signaled
327     * by onBindFailed(), in this case mService will be null). These two events can happen in any
328     * order.
329     */
330    private void doConnectionSetup() {
331        TraceEvent.begin();
332        assert mServiceConnectComplete && mConnectionParams != null;
333
334        if (mService != null) {
335            Bundle bundle = new Bundle();
336            bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine);
337
338            FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped;
339            ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileInfos.length];
340            for (int i = 0; i < fileInfos.length; i++) {
341                if (fileInfos[i].mFd == -1) {
342                    // If someone provided an invalid FD, they are doing something wrong.
343                    Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for process connection, "
344                          + "aborting connection.");
345                    return;
346                }
347                String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX;
348                String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX;
349                if (fileInfos[i].mAutoClose) {
350                    // Adopt the FD, it will be closed when we close the ParcelFileDescriptor.
351                    parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].mFd);
352                } else {
353                    try {
354                        parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i].mFd);
355                    } catch (IOException e) {
356                        Log.e(TAG,
357                              "Invalid FD provided for process connection, aborting connection.",
358                              e);
359                        return;
360                    }
361
362                }
363                bundle.putParcelable(fdName, parcelFiles[i]);
364                bundle.putInt(idName, fileInfos[i].mId);
365            }
366            // Add the CPU properties now.
367            bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount());
368            bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask());
369
370            bundle.putBundle(Linker.EXTRA_LINKER_SHARED_RELROS,
371                             mConnectionParams.mSharedRelros);
372
373            try {
374                mPID = mService.setupConnection(bundle, mConnectionParams.mCallback);
375            } catch (android.os.RemoteException re) {
376                Log.e(TAG, "Failed to setup connection.", re);
377            }
378            // We proactively close the FDs rather than wait for GC & finalizer.
379            try {
380                for (ParcelFileDescriptor parcelFile : parcelFiles) {
381                    if (parcelFile != null) parcelFile.close();
382                }
383            } catch (IOException ioe) {
384                Log.w(TAG, "Failed to close FD.", ioe);
385            }
386        }
387        mConnectionParams = null;
388
389        if (mConnectionCallback != null) {
390            mConnectionCallback.onConnected(getPid());
391        }
392        TraceEvent.end();
393    }
394
395    /** @return true iff the initial oom binding is currently bound. */
396    boolean isInitialBindingBound() {
397        synchronized (mLock) {
398            return mInitialBinding.isBound();
399        }
400    }
401
402    /** @return true iff the strong oom binding is currently bound. */
403    boolean isStrongBindingBound() {
404        synchronized (mLock) {
405            return mStrongBinding.isBound();
406        }
407    }
408
409    /**
410     * Called to remove the strong binding estabilished when the connection was started. It is safe
411     * to call this multiple times.
412     */
413    void removeInitialBinding() {
414        synchronized (mLock) {
415            mInitialBinding.unbind();
416        }
417    }
418
419    /**
420     * Unbinds the bindings that protect the process from oom killing. It is safe to call this
421     * multiple times, before as well as after stop().
422     */
423    void dropOomBindings() {
424        synchronized (mLock) {
425            mInitialBinding.unbind();
426
427            mAttachAsActiveCount = 0;
428            mStrongBinding.unbind();
429        }
430    }
431
432    /**
433     * Called when the service becomes active, ie important to the caller. This is handled by
434     * setting up a binding that will make the service as important as the main process. We allow
435     * callers to indicate the same connection as active multiple times. Instead of maintaining
436     * multiple bindings, we count the requests and unbind when the count drops to zero.
437     */
438    void attachAsActive() {
439        synchronized (mLock) {
440            if (mService == null) {
441                Log.w(TAG, "The connection is not bound for " + mPID);
442                return;
443            }
444            if (mAttachAsActiveCount == 0) {
445                mStrongBinding.bind(null);
446            }
447            mAttachAsActiveCount++;
448        }
449    }
450
451    /**
452     * Called when the service is no longer considered active.
453     */
454    void detachAsActive() {
455        synchronized (mLock) {
456            if (mService == null) {
457                Log.w(TAG, "The connection is not bound for " + mPID);
458                return;
459            }
460            assert mAttachAsActiveCount > 0;
461            mAttachAsActiveCount--;
462            if (mAttachAsActiveCount == 0) {
463                mStrongBinding.unbind();
464            }
465        }
466    }
467
468    /**
469     * @return The connection PID, or 0 if not yet connected.
470     */
471    int getPid() {
472        synchronized (mLock) {
473            return mPID;
474        }
475    }
476}
477