1 // Copyright 2013 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.
5 package org.chromium.content.browser;
7 import android.content.ComponentName;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.ServiceConnection;
11 import android.os.Bundle;
12 import android.os.DeadObjectException;
13 import android.os.IBinder;
14 import android.os.ParcelFileDescriptor;
15 import android.os.RemoteException;
16 import android.util.Log;
18 import org.chromium.base.CpuFeatures;
19 import org.chromium.base.ThreadUtils;
20 import org.chromium.base.TraceEvent;
21 import org.chromium.base.VisibleForTesting;
22 import org.chromium.base.library_loader.Linker;
23 import org.chromium.content.app.ChildProcessService;
24 import org.chromium.content.app.ChromiumLinkerParams;
25 import org.chromium.content.common.IChildProcessCallback;
26 import org.chromium.content.common.IChildProcessService;
28 import java.io.IOException;
31 * Manages a connection between the browser activity and a child service.
33 public class ChildProcessConnectionImpl implements ChildProcessConnection {
34 private final Context mContext;
35 private final int mServiceNumber;
36 private final boolean mInSandbox;
37 private final ChildProcessConnection.DeathCallback mDeathCallback;
38 private final Class<? extends ChildProcessService> mServiceClass;
40 // Synchronization: While most internal flow occurs on the UI thread, the public API
41 // (specifically start and stop) may be called from any thread, hence all entry point methods
42 // into the class are synchronized on the lock to protect access to these members.
43 private final Object mLock = new Object();
44 private IChildProcessService mService = null;
45 // Set to true when the service connected successfully.
46 private boolean mServiceConnectComplete = false;
47 // Set to true when the service disconnects, as opposed to being properly closed. This happens
48 // when the process crashes or gets killed by the system out-of-memory killer.
49 private boolean mServiceDisconnected = false;
50 // When the service disconnects (i.e. mServiceDisconnected is set to true), the status of the
51 // oom bindings is stashed here for future inspection.
52 private boolean mWasOomProtected = false;
53 private int mPid = 0; // Process ID of the corresponding child process.
54 // Initial binding protects the newly spawned process from being killed before it is put to use,
55 // it is maintained between calls to start() and removeInitialBinding().
56 private ChildServiceConnection mInitialBinding = null;
57 // Strong binding will make the service priority equal to the priority of the activity. We want
58 // the OS to be able to kill background renderers as it kills other background apps, so strong
59 // bindings are maintained only for services that are active at the moment (between
60 // addStrongBinding() and removeStrongBinding()).
61 private ChildServiceConnection mStrongBinding = null;
62 // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
63 // to start() and stop().
64 private ChildServiceConnection mWaivedBinding = null;
65 // Incremented on addStrongBinding(), decremented on removeStrongBinding().
66 private int mStrongBindingCount = 0;
68 // Linker-related parameters.
69 private ChromiumLinkerParams mLinkerParams = null;
71 private static final String TAG = "ChildProcessConnection";
73 private static class ConnectionParams {
74 final String[] mCommandLine;
75 final FileDescriptorInfo[] mFilesToBeMapped;
76 final IChildProcessCallback mCallback;
77 final Bundle mSharedRelros;
79 ConnectionParams(String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
80 IChildProcessCallback callback, Bundle sharedRelros) {
81 mCommandLine = commandLine;
82 mFilesToBeMapped = filesToBeMapped;
84 mSharedRelros = sharedRelros;
88 // This is set in setupConnection() and is later used in doConnectionSetupLocked(), after which
89 // the variable is cleared. Therefore this is only valid while the connection is being set up.
90 private ConnectionParams mConnectionParams;
92 // Callback provided in setupConnection() that will communicate the result to the caller. This
93 // has to be called exactly once after setupConnection(), even if setup fails, so that the
94 // caller can free up resources associated with the setup attempt. This is set to null after the
96 private ChildProcessConnection.ConnectionCallback mConnectionCallback;
98 private class ChildServiceConnection implements ServiceConnection {
99 private boolean mBound = false;
101 private final int mBindFlags;
103 private Intent createServiceBindIntent() {
104 Intent intent = new Intent();
105 intent.setClassName(mContext, mServiceClass.getName() + mServiceNumber);
106 intent.setPackage(mContext.getPackageName());
110 public ChildServiceConnection(int bindFlags) {
111 mBindFlags = bindFlags;
114 boolean bind(String[] commandLine) {
117 final Intent intent = createServiceBindIntent();
118 if (commandLine != null) {
119 intent.putExtra(EXTRA_COMMAND_LINE, commandLine);
121 if (mLinkerParams != null)
122 mLinkerParams.addIntentExtras(intent);
123 mBound = mContext.bindService(intent, this, mBindFlags);
131 mContext.unbindService(this);
141 public void onServiceConnected(ComponentName className, IBinder service) {
142 synchronized (mLock) {
143 // A flag from the parent class ensures we run the post-connection logic only once
144 // (instead of once per each ChildServiceConnection).
145 if (mServiceConnectComplete) {
149 mServiceConnectComplete = true;
150 mService = IChildProcessService.Stub.asInterface(service);
151 // Run the setup if the connection parameters have already been provided. If not,
152 // doConnectionSetupLocked() will be called from setupConnection().
153 if (mConnectionParams != null) {
154 doConnectionSetupLocked();
161 // Called on the main thread to notify that the child service did not disconnect gracefully.
163 public void onServiceDisconnected(ComponentName className) {
164 synchronized (mLock) {
165 // Ensure that the disconnection logic runs only once (instead of once per each
166 // ChildServiceConnection).
167 if (mServiceDisconnected) {
170 mServiceDisconnected = true;
171 // Stash the status of the oom bindings, since stop() will release all bindings.
172 mWasOomProtected = mInitialBinding.isBound() || mStrongBinding.isBound();
173 Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=" + mPid);
174 stop(); // We don't want to auto-restart on crash. Let the browser do that.
175 mDeathCallback.onChildProcessDied(ChildProcessConnectionImpl.this);
176 // If we have a pending connection callback, we need to communicate the failure to
178 if (mConnectionCallback != null) {
179 mConnectionCallback.onConnected(0);
181 mConnectionCallback = null;
186 ChildProcessConnectionImpl(Context context, int number, boolean inSandbox,
187 ChildProcessConnection.DeathCallback deathCallback,
188 Class<? extends ChildProcessService> serviceClass,
189 ChromiumLinkerParams chromiumLinkerParams) {
191 mServiceNumber = number;
192 mInSandbox = inSandbox;
193 mDeathCallback = deathCallback;
194 mServiceClass = serviceClass;
195 mLinkerParams = chromiumLinkerParams;
196 mInitialBinding = new ChildServiceConnection(Context.BIND_AUTO_CREATE);
197 mStrongBinding = new ChildServiceConnection(
198 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
199 mWaivedBinding = new ChildServiceConnection(
200 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
204 public int getServiceNumber() {
205 return mServiceNumber;
209 public boolean isInSandbox() {
214 public IChildProcessService getService() {
215 synchronized (mLock) {
221 public int getPid() {
222 synchronized (mLock) {
228 public void start(String[] commandLine) {
229 synchronized (mLock) {
231 assert !ThreadUtils.runningOnUiThread();
232 assert mConnectionParams == null :
233 "setupConnection() called before start() in ChildProcessConnectionImpl.";
235 if (!mInitialBinding.bind(commandLine)) {
236 Log.e(TAG, "Failed to establish the service connection.");
237 // We have to notify the caller so that they can free-up associated resources.
238 // TODO(ppi): Can we hard-fail here?
239 mDeathCallback.onChildProcessDied(ChildProcessConnectionImpl.this);
241 mWaivedBinding.bind(null);
248 public void setupConnection(
249 String[] commandLine,
250 FileDescriptorInfo[] filesToBeMapped,
251 IChildProcessCallback processCallback,
252 ConnectionCallback connectionCallback,
253 Bundle sharedRelros) {
254 synchronized (mLock) {
255 assert mConnectionParams == null;
256 if (mServiceDisconnected) {
257 Log.w(TAG, "Tried to setup a connection that already disconnected.");
258 connectionCallback.onConnected(0);
263 mConnectionCallback = connectionCallback;
264 mConnectionParams = new ConnectionParams(
265 commandLine, filesToBeMapped, processCallback, sharedRelros);
266 // Run the setup if the service is already connected. If not, doConnectionSetupLocked()
267 // will be called from onServiceConnected().
268 if (mServiceConnectComplete) {
269 doConnectionSetupLocked();
277 synchronized (mLock) {
278 mInitialBinding.unbind();
279 mStrongBinding.unbind();
280 mWaivedBinding.unbind();
281 mStrongBindingCount = 0;
282 if (mService != null) {
285 mConnectionParams = null;
290 * Called after the connection parameters have been set (in setupConnection()) *and* a
291 * connection has been established (as signaled by onServiceConnected()). These two events can
292 * happen in any order. Has to be called with mLock.
294 private void doConnectionSetupLocked() {
296 assert mServiceConnectComplete && mService != null;
297 assert mConnectionParams != null;
299 Bundle bundle = new Bundle();
300 bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine);
302 FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped;
303 ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileInfos.length];
304 for (int i = 0; i < fileInfos.length; i++) {
305 if (fileInfos[i].mFd == -1) {
306 // If someone provided an invalid FD, they are doing something wrong.
307 Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for process connection, "
308 + "aborting connection.");
311 String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX;
312 String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX;
313 if (fileInfos[i].mAutoClose) {
314 // Adopt the FD, it will be closed when we close the ParcelFileDescriptor.
315 parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].mFd);
318 parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i].mFd);
319 } catch (IOException e) {
321 "Invalid FD provided for process connection, aborting connection.",
327 bundle.putParcelable(fdName, parcelFiles[i]);
328 bundle.putInt(idName, fileInfos[i].mId);
330 // Add the CPU properties now.
331 bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount());
332 bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask());
334 bundle.putBundle(Linker.EXTRA_LINKER_SHARED_RELROS,
335 mConnectionParams.mSharedRelros);
338 mPid = mService.setupConnection(bundle, mConnectionParams.mCallback);
339 assert mPid != 0 : "Child service claims to be run by a process of pid=0.";
340 } catch (android.os.RemoteException re) {
341 Log.e(TAG, "Failed to setup connection.", re);
343 // We proactively close the FDs rather than wait for GC & finalizer.
345 for (ParcelFileDescriptor parcelFile : parcelFiles) {
346 if (parcelFile != null) parcelFile.close();
348 } catch (IOException ioe) {
349 Log.w(TAG, "Failed to close FD.", ioe);
351 mConnectionParams = null;
353 if (mConnectionCallback != null) {
354 mConnectionCallback.onConnected(mPid);
356 mConnectionCallback = null;
361 public boolean isInitialBindingBound() {
362 synchronized (mLock) {
363 return mInitialBinding.isBound();
368 public boolean isStrongBindingBound() {
369 synchronized (mLock) {
370 return mStrongBinding.isBound();
375 public void removeInitialBinding() {
376 synchronized (mLock) {
377 mInitialBinding.unbind();
382 public boolean isOomProtectedOrWasWhenDied() {
383 synchronized (mLock) {
384 if (mServiceDisconnected) {
385 return mWasOomProtected;
387 return mInitialBinding.isBound() || mStrongBinding.isBound();
393 public void dropOomBindings() {
394 synchronized (mLock) {
395 mInitialBinding.unbind();
397 mStrongBindingCount = 0;
398 mStrongBinding.unbind();
403 public void addStrongBinding() {
404 synchronized (mLock) {
405 if (mService == null) {
406 Log.w(TAG, "The connection is not bound for " + mPid);
409 if (mStrongBindingCount == 0) {
410 mStrongBinding.bind(null);
412 mStrongBindingCount++;
417 public void removeStrongBinding() {
418 synchronized (mLock) {
419 if (mService == null) {
420 Log.w(TAG, "The connection is not bound for " + mPid);
423 assert mStrongBindingCount > 0;
424 mStrongBindingCount--;
425 if (mStrongBindingCount == 0) {
426 mStrongBinding.unbind();
432 public boolean crashServiceForTesting() throws RemoteException {
434 mService.crashIntentionallyForTesting();
435 } catch (DeadObjectException e) {
442 public boolean isConnected() {
443 return mService != null;