package org.chromium.content.browser;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.chromium.base.CalledByNative;
-import org.chromium.base.CpuFeatures;
-import org.chromium.base.SysUtils;
-import org.chromium.base.ThreadUtils;
-import org.chromium.content.app.ChildProcessService;
-import org.chromium.content.app.Linker;
-import org.chromium.content.app.LinkerParams;
-import org.chromium.content.common.CommandLine;
import org.chromium.content.common.IChildProcessCallback;
import org.chromium.content.common.IChildProcessService;
-import org.chromium.content.common.TraceEvent;
/**
- * Manages a connection between the browser activity and a child service. The class is responsible
- * for estabilishing the connection (start()), closing it (stop()) and increasing the priority of
- * the service when it is in active use (between calls to attachAsActive() and detachAsActive()).
+ * Manages a connection between the browser activity and a child service. ChildProcessConnection is
+ * responsible for estabilishing the connection (start()), closing it (stop()) and manipulating the
+ * bindings held onto the service (addStrongBinding(), removeStrongBinding(),
+ * removeInitialBinding()).
*/
-public class ChildProcessConnection {
+public interface ChildProcessConnection {
/**
* Used to notify the consumer about disconnection of the service. This callback is provided
* earlier than ConnectionCallbacks below, as a child process might die before the connection is
* fully set up.
*/
interface DeathCallback {
- void onChildProcessDied(int pid);
+ void onChildProcessDied(ChildProcessConnection connection);
}
/**
interface ConnectionCallback {
/**
* Called when the connection to the service is established.
- * @param pid Pid of the child process.
+ * @param pid the pid of the child process
*/
void onConnected(int pid);
}
public static final String EXTRA_CPU_FEATURES =
"com.google.android.apps.chrome.extra.cpu_features";
- private final Context mContext;
- private final int mServiceNumber;
- private final boolean mInSandbox;
- private final ChildProcessConnection.DeathCallback mDeathCallback;
- private final Class<? extends ChildProcessService> mServiceClass;
-
- // Synchronization: While most internal flow occurs on the UI thread, the public API
- // (specifically start and stop) may be called from any thread, hence all entry point methods
- // into the class are synchronized on the lock to protect access to these members. But see also
- // the TODO where AsyncBoundServiceConnection is created.
- private final Object mLock = new Object();
- private IChildProcessService mService = null;
- // Set to true when the service connect is finished, even if it fails.
- private boolean mServiceConnectComplete = false;
- // Set to true when the service disconnects, as opposed to being properly closed. This happens
- // when the process crashes or gets killed by the system out-of-memory killer.
- private boolean mServiceDisconnected = false;
- private int mPID = 0; // Process ID of the corresponding child process.
- // Initial binding protects the newly spawned process from being killed before it is put to use,
- // it is maintained between calls to start() and removeInitialBinding().
- private ChildServiceConnection mInitialBinding = null;
- // Strong binding will make the service priority equal to the priority of the activity. We want
- // the OS to be able to kill background renderers as it kills other background apps, so strong
- // bindings are maintained only for services that are active at the moment (between
- // attachAsActive() and detachAsActive()).
- private ChildServiceConnection mStrongBinding = null;
- // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
- // to start() and stop().
- private ChildServiceConnection mWaivedBinding = null;
- // Incremented on attachAsActive(), decremented on detachAsActive().
- private int mAttachAsActiveCount = 0;
-
- // Linker-related parameters.
- private LinkerParams mLinkerParams = null;
-
- private static final String TAG = "ChildProcessConnection";
-
- private static class ConnectionParams {
- final String[] mCommandLine;
- final FileDescriptorInfo[] mFilesToBeMapped;
- final IChildProcessCallback mCallback;
- final Bundle mSharedRelros;
-
- ConnectionParams(String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
- IChildProcessCallback callback, Bundle sharedRelros) {
- mCommandLine = commandLine;
- mFilesToBeMapped = filesToBeMapped;
- mCallback = callback;
- mSharedRelros = sharedRelros;
- }
- }
-
- // This is set by the consumer of the class in setupConnection() and is later used in
- // doSetupConnection(), after which the variable is cleared. Therefore this is only valid while
- // the connection is being set up.
- private ConnectionParams mConnectionParams;
-
- // Callbacks used to notify the consumer about connection events. This is also provided in
- // setupConnection(), but remains valid after setup.
- private ChildProcessConnection.ConnectionCallback mConnectionCallback;
-
- private class ChildServiceConnection implements ServiceConnection {
- private boolean mBound = false;
-
- private final int mBindFlags;
-
- public ChildServiceConnection(int bindFlags) {
- mBindFlags = bindFlags;
- }
-
- boolean bind(String[] commandLine) {
- if (!mBound) {
- final Intent intent = createServiceBindIntent();
- if (commandLine != null) {
- intent.putExtra(EXTRA_COMMAND_LINE, commandLine);
- }
- if (mLinkerParams != null)
- mLinkerParams.addIntentExtras(intent);
- mBound = mContext.bindService(intent, this, mBindFlags);
- }
- return mBound;
- }
-
- void unbind() {
- if (mBound) {
- mContext.unbindService(this);
- mBound = false;
- }
- }
+ int getServiceNumber();
- boolean isBound() {
- return mBound;
- }
+ boolean isInSandbox();
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- synchronized(mLock) {
- // A flag from the parent class ensures we run the post-connection logic only once
- // (instead of once per each ChildServiceConnection).
- if (mServiceConnectComplete) {
- return;
- }
- TraceEvent.begin();
- mServiceConnectComplete = true;
- mService = IChildProcessService.Stub.asInterface(service);
- // Make sure that the connection parameters have already been provided. If not,
- // doConnectionSetup() will be called from setupConnection().
- if (mConnectionParams != null) {
- doConnectionSetup();
- }
- TraceEvent.end();
- }
- }
+ IChildProcessService getService();
-
- // Called on the main thread to notify that the child service did not disconnect gracefully.
- @Override
- public void onServiceDisconnected(ComponentName className) {
- // Ensure that the disconnection logic runs only once (instead of once per each
- // ChildServiceConnection).
- if (mServiceDisconnected) {
- return;
- }
- mServiceDisconnected = true;
- int pid = mPID; // Stash the pid for DeathCallback since stop() will clear it.
- boolean disconnectedWhileBeingSetUp = mConnectionParams != null;
- Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=" + pid);
- stop(); // We don't want to auto-restart on crash. Let the browser do that.
- if (pid != 0) {
- mDeathCallback.onChildProcessDied(pid);
- }
- // TODO(ppi): does anyone know why we need to do that?
- if (disconnectedWhileBeingSetUp && mConnectionCallback != null) {
- mConnectionCallback.onConnected(0);
- }
- }
- }
-
- ChildProcessConnection(Context context, int number, boolean inSandbox,
- ChildProcessConnection.DeathCallback deathCallback,
- Class<? extends ChildProcessService> serviceClass,
- LinkerParams linkerParams) {
- mContext = context;
- mServiceNumber = number;
- mInSandbox = inSandbox;
- mDeathCallback = deathCallback;
- mServiceClass = serviceClass;
- mLinkerParams = linkerParams;
- mInitialBinding = new ChildServiceConnection(Context.BIND_AUTO_CREATE);
- mStrongBinding = new ChildServiceConnection(
- Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
- mWaivedBinding = new ChildServiceConnection(
- Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
- }
-
- int getServiceNumber() {
- return mServiceNumber;
- }
-
- boolean isInSandbox() {
- return mInSandbox;
- }
-
- IChildProcessService getService() {
- synchronized(mLock) {
- return mService;
- }
- }
-
- private Intent createServiceBindIntent() {
- Intent intent = new Intent();
- intent.setClassName(mContext, mServiceClass.getName() + mServiceNumber);
- intent.setPackage(mContext.getPackageName());
- return intent;
- }
+ /**
+ * @return the connection pid, or 0 if not yet connected
+ */
+ int getPid();
/**
* Starts a connection to an IChildProcessService. This must be followed by a call to
* setupConnection() to setup the connection parameters. start() and setupConnection() are
- * separate to allow the client to pass whatever parameters they have available here, and
- * complete the remainder later while reducing the connection setup latency.
- * @param commandLine (Optional) Command line for the child process. If omitted, then
+ * separate to allow to pass whatever parameters are available in start(), and complete the
+ * remainder later while reducing the connection setup latency.
+ * @param commandLine (optional) command line for the child process. If omitted, then
* the command line parameters must instead be passed to setupConnection().
*/
- void start(String[] commandLine) {
- synchronized(mLock) {
- TraceEvent.begin();
- assert !ThreadUtils.runningOnUiThread();
-
- if (!mInitialBinding.bind(commandLine)) {
- onBindFailed();
- } else {
- mWaivedBinding.bind(null);
- }
- TraceEvent.end();
- }
- }
+ void start(String[] commandLine);
/**
- * Setups the connection after it was started with start(). This method should be called by the
- * consumer of the class to set up additional connection parameters.
- * @param commandLine (Optional) will be ignored if the command line was already sent in bind()
- * @param fileToBeMapped a list of file descriptors that should be registered
- * @param callback Used for status updates regarding this process connection.
- * @param connectionCallbacks will notify the consumer about the connection being established
- * and the status of the out-of-memory bindings being bound for the connection.
+ * Setups the connection after it was started with start().
+ * @param commandLine (optional) will be ignored if the command line was already sent in start()
+ * @param filesToBeMapped a list of file descriptors that should be registered
+ * @param processCallback used for status updates regarding this process connection
+ * @param connectionCallback will be called exactly once after the connection is set up or the
+ * setup fails
*/
void setupConnection(
String[] commandLine,
FileDescriptorInfo[] filesToBeMapped,
IChildProcessCallback processCallback,
- ConnectionCallback connectionCallbacks,
- Bundle sharedRelros) {
- synchronized(mLock) {
- TraceEvent.begin();
- assert mConnectionParams == null;
- mConnectionCallback = connectionCallbacks;
- mConnectionParams = new ConnectionParams(
- commandLine, filesToBeMapped, processCallback, sharedRelros);
- // Make sure that the service is already connected. If not, doConnectionSetup() will be
- // called from onServiceConnected().
- if (mServiceConnectComplete) {
- doConnectionSetup();
- }
- TraceEvent.end();
- }
- }
+ ConnectionCallback connectionCallback,
+ Bundle sharedRelros);
/**
* Terminates the connection to IChildProcessService, closing all bindings. It is safe to call
* this multiple times.
*/
- void stop() {
- synchronized(mLock) {
- mInitialBinding.unbind();
- mStrongBinding.unbind();
- mWaivedBinding.unbind();
- mAttachAsActiveCount = 0;
- if (mService != null) {
- mService = null;
- mPID = 0;
- }
- mConnectionParams = null;
- mServiceConnectComplete = false;
- }
- }
-
- // Called on the main thread to notify that the bindService() call failed (returned false).
- private void onBindFailed() {
- mServiceConnectComplete = true;
- if (mConnectionParams != null) {
- doConnectionSetup();
- }
- }
-
- /**
- * Called after the connection parameters have been set (in setupConnection()) *and* a
- * connection has been established (as signaled by onServiceConnected()) or failed (as signaled
- * by onBindFailed(), in this case mService will be null). These two events can happen in any
- * order.
- */
- private void doConnectionSetup() {
- TraceEvent.begin();
- assert mServiceConnectComplete && mConnectionParams != null;
-
- if (mService != null) {
- Bundle bundle = new Bundle();
- bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine);
-
- FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped;
- ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileInfos.length];
- for (int i = 0; i < fileInfos.length; i++) {
- if (fileInfos[i].mFd == -1) {
- // If someone provided an invalid FD, they are doing something wrong.
- Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for process connection, "
- + "aborting connection.");
- return;
- }
- String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX;
- String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX;
- if (fileInfos[i].mAutoClose) {
- // Adopt the FD, it will be closed when we close the ParcelFileDescriptor.
- parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].mFd);
- } else {
- try {
- parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i].mFd);
- } catch(IOException e) {
- Log.e(TAG,
- "Invalid FD provided for process connection, aborting connection.",
- e);
- return;
- }
-
- }
- bundle.putParcelable(fdName, parcelFiles[i]);
- bundle.putInt(idName, fileInfos[i].mId);
- }
- // Add the CPU properties now.
- bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount());
- bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask());
-
- bundle.putBundle(Linker.EXTRA_LINKER_SHARED_RELROS,
- mConnectionParams.mSharedRelros);
-
- try {
- mPID = mService.setupConnection(bundle, mConnectionParams.mCallback);
- } catch (android.os.RemoteException re) {
- Log.e(TAG, "Failed to setup connection.", re);
- }
- // We proactively close the FDs rather than wait for GC & finalizer.
- try {
- for (ParcelFileDescriptor parcelFile : parcelFiles) {
- if (parcelFile != null) parcelFile.close();
- }
- } catch (IOException ioe) {
- Log.w(TAG, "Failed to close FD.", ioe);
- }
- }
- mConnectionParams = null;
-
- if (mConnectionCallback != null) {
- mConnectionCallback.onConnected(getPid());
- }
- TraceEvent.end();
- }
+ void stop();
/** @return true iff the initial oom binding is currently bound. */
- boolean isInitialBindingBound() {
- synchronized(mLock) {
- return mInitialBinding.isBound();
- }
- }
+ boolean isInitialBindingBound();
/** @return true iff the strong oom binding is currently bound. */
- boolean isStrongBindingBound() {
- synchronized(mLock) {
- return mStrongBinding.isBound();
- }
- }
+ boolean isStrongBindingBound();
/**
* Called to remove the strong binding estabilished when the connection was started. It is safe
* to call this multiple times.
*/
- void removeInitialBinding() {
- synchronized(mLock) {
- mInitialBinding.unbind();
- }
- }
+ void removeInitialBinding();
/**
- * Unbinds the bindings that protect the process from oom killing. It is safe to call this
- * multiple times, before as well as after stop().
+ * For live connections, this returns true iff either the initial or the strong binding is
+ * bound, i.e. the connection has at least one oom binding. For connections that disconnected
+ * (did not exit properly), this returns true iff the connection had at least one oom binding
+ * when it disconnected.
*/
- void dropOomBindings() {
- synchronized(mLock) {
- mInitialBinding.unbind();
-
- mAttachAsActiveCount = 0;
- mStrongBinding.unbind();
- }
- }
+ boolean isOomProtectedOrWasWhenDied();
/**
- * Called when the service becomes active, ie important to the caller. This is handled by
- * setting up a binding that will make the service as important as the main process. We allow
- * callers to indicate the same connection as active multiple times. Instead of maintaining
- * multiple bindings, we count the requests and unbind when the count drops to zero.
+ * Unbinds the bindings that protect the process from oom killing. It is safe to call this
+ * multiple times, before as well as after stop().
*/
- void attachAsActive() {
- synchronized(mLock) {
- if (mService == null) {
- Log.w(TAG, "The connection is not bound for " + mPID);
- return;
- }
- if (mAttachAsActiveCount == 0) {
- mStrongBinding.bind(null);
- }
- mAttachAsActiveCount++;
- }
- }
+ void dropOomBindings();
/**
- * Called when the service is no longer considered active.
+ * Attaches a strong binding that will make the service as important as the main process. Each
+ * call should be succeeded by removeStrongBinding(), but multiple strong bindings can be
+ * requested and released independently.
*/
- void detachAsActive() {
- synchronized(mLock) {
- if (mService == null) {
- Log.w(TAG, "The connection is not bound for " + mPID);
- return;
- }
- assert mAttachAsActiveCount > 0;
- mAttachAsActiveCount--;
- if (mAttachAsActiveCount == 0) {
- mStrongBinding.unbind();
- }
- }
- }
+ void addStrongBinding();
/**
- * @return The connection PID, or 0 if not yet connected.
+ * Called when the service is no longer in active use of the consumer.
*/
- int getPid() {
- synchronized(mLock) {
- return mPID;
- }
- }
+ void removeStrongBinding();
}