summaryrefslogtreecommitdiff
path: root/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java
diff options
context:
space:
mode:
Diffstat (limited to 'base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java')
-rw-r--r--base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java278
1 files changed, 278 insertions, 0 deletions
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java
new file mode 100644
index 0000000000..7cdc8528bd
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java
@@ -0,0 +1,278 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.process_launcher;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.TraceEvent;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This class is used to start a child process by connecting to a ChildProcessService.
+ */
+public class ChildProcessLauncher {
+ private static final String TAG = "ChildProcLauncher";
+
+ /** Delegate that client should use to customize the process launching. */
+ public abstract static class Delegate {
+ /**
+ * Called when the launcher is about to start. Gives the embedder a chance to provide an
+ * already bound connection if it has one. (allowing for warm-up connections: connections
+ * that are already bound in advance to speed up child process start-up time).
+ * Note that onBeforeConnectionAllocated will not be called if this method returns a
+ * connection.
+ * @param connectionAllocator the allocator the returned connection should have been
+ * allocated of.
+ * @param serviceCallback the service callback that the connection should use.
+ * @return a bound connection to use to connect to the child process service, or null if a
+ * connection should be allocated and bound by the launcher.
+ */
+ public ChildProcessConnection getBoundConnection(
+ ChildConnectionAllocator connectionAllocator,
+ ChildProcessConnection.ServiceCallback serviceCallback) {
+ return null;
+ }
+
+ /**
+ * Called before a connection is allocated.
+ * Note that this is only called if the ChildProcessLauncher is created with
+ * {@link #createWithConnectionAllocator}.
+ * @param serviceBundle the bundle passed in the service intent. Clients can add their own
+ * extras to the bundle.
+ */
+ public void onBeforeConnectionAllocated(Bundle serviceBundle) {}
+
+ /**
+ * Called before setup is called on the connection.
+ * @param connectionBundle the bundle passed to the {@link ChildProcessService} in the
+ * setup call. Clients can add their own extras to the bundle.
+ */
+ public void onBeforeConnectionSetup(Bundle connectionBundle) {}
+
+ /**
+ * Called when the connection was successfully established, meaning the setup call on the
+ * service was successful.
+ * @param connection the connection over which the setup call was made.
+ */
+ public void onConnectionEstablished(ChildProcessConnection connection) {}
+
+ /**
+ * Called when a connection has been disconnected. Only invoked if onConnectionEstablished
+ * was called, meaning the connection was already established.
+ * @param connection the connection that got disconnected.
+ */
+ public void onConnectionLost(ChildProcessConnection connection) {}
+ }
+
+ // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
+ private static final int NULL_PROCESS_HANDLE = 0;
+
+ // The handle for the thread we were created on and on which all methods should be called.
+ private final Handler mLauncherHandler;
+
+ private final Delegate mDelegate;
+
+ private final String[] mCommandLine;
+ private final FileDescriptorInfo[] mFilesToBeMapped;
+
+ // The allocator used to create the connection.
+ private final ChildConnectionAllocator mConnectionAllocator;
+
+ // The IBinder interfaces provided to the created service.
+ private final List<IBinder> mClientInterfaces;
+
+ // The actual service connection. Set once we have connected to the service.
+ private ChildProcessConnection mConnection;
+
+ /**
+ * Constructor.
+ *
+ * @param launcherHandler the handler for the thread where all operations should happen.
+ * @param delegate the delagate that gets notified of the launch progress.
+ * @param commandLine the command line that should be passed to the started process.
+ * @param filesToBeMapped the files that should be passed to the started process.
+ * @param connectionAllocator the allocator used to create connections to the service.
+ * @param clientInterfaces the interfaces that should be passed to the started process so it can
+ * communicate with the parent process.
+ */
+ public ChildProcessLauncher(Handler launcherHandler, Delegate delegate, String[] commandLine,
+ FileDescriptorInfo[] filesToBeMapped, ChildConnectionAllocator connectionAllocator,
+ List<IBinder> clientInterfaces) {
+ assert connectionAllocator != null;
+ mLauncherHandler = launcherHandler;
+ isRunningOnLauncherThread();
+ mCommandLine = commandLine;
+ mConnectionAllocator = connectionAllocator;
+ mDelegate = delegate;
+ mFilesToBeMapped = filesToBeMapped;
+ mClientInterfaces = clientInterfaces;
+ }
+
+ /**
+ * Starts the child process and calls setup on it if {@param setupConnection} is true.
+ * @param setupConnection whether the setup should be performed on the connection once
+ * established
+ * @param queueIfNoFreeConnection whether to queue that request if no service connection is
+ * available. If the launcher was created with a connection provider, this parameter has no
+ * effect.
+ * @return true if the connection was started or was queued.
+ */
+ public boolean start(final boolean setupConnection, final boolean queueIfNoFreeConnection) {
+ assert isRunningOnLauncherThread();
+ try {
+ TraceEvent.begin("ChildProcessLauncher.start");
+ ChildProcessConnection.ServiceCallback serviceCallback =
+ new ChildProcessConnection.ServiceCallback() {
+ @Override
+ public void onChildStarted() {}
+
+ @Override
+ public void onChildStartFailed(ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ assert mConnection == connection;
+ Log.e(TAG, "ChildProcessConnection.start failed, trying again");
+ mLauncherHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // The child process may already be bound to another client
+ // (this can happen if multi-process WebView is used in more
+ // than one process), so try starting the process again.
+ // This connection that failed to start has not been freed,
+ // so a new bound connection will be allocated.
+ mConnection = null;
+ start(setupConnection, queueIfNoFreeConnection);
+ }
+ });
+ }
+
+ @Override
+ public void onChildProcessDied(ChildProcessConnection connection) {
+ assert isRunningOnLauncherThread();
+ assert mConnection == connection;
+ ChildProcessLauncher.this.onChildProcessDied();
+ }
+ };
+ mConnection = mDelegate.getBoundConnection(mConnectionAllocator, serviceCallback);
+ if (mConnection != null) {
+ assert mConnectionAllocator.isConnectionFromAllocator(mConnection);
+ setupConnection();
+ return true;
+ }
+ if (!allocateAndSetupConnection(
+ serviceCallback, setupConnection, queueIfNoFreeConnection)
+ && !queueIfNoFreeConnection) {
+ return false;
+ }
+ return true;
+ } finally {
+ TraceEvent.end("ChildProcessLauncher.start");
+ }
+ }
+
+ public ChildProcessConnection getConnection() {
+ return mConnection;
+ }
+
+ public ChildConnectionAllocator getConnectionAllocator() {
+ return mConnectionAllocator;
+ }
+
+ private boolean allocateAndSetupConnection(
+ final ChildProcessConnection.ServiceCallback serviceCallback,
+ final boolean setupConnection, final boolean queueIfNoFreeConnection) {
+ assert mConnection == null;
+ Bundle serviceBundle = new Bundle();
+ mDelegate.onBeforeConnectionAllocated(serviceBundle);
+
+ mConnection = mConnectionAllocator.allocate(
+ ContextUtils.getApplicationContext(), serviceBundle, serviceCallback);
+ if (mConnection == null) {
+ if (!queueIfNoFreeConnection) {
+ Log.d(TAG, "Failed to allocate a child connection (no queuing).");
+ return false;
+ }
+ mConnectionAllocator.queueAllocation(
+ () -> allocateAndSetupConnection(
+ serviceCallback, setupConnection, queueIfNoFreeConnection));
+ return false;
+ }
+
+ if (setupConnection) {
+ setupConnection();
+ }
+ return true;
+ }
+
+ private void setupConnection() {
+ ChildProcessConnection.ConnectionCallback connectionCallback =
+ new ChildProcessConnection.ConnectionCallback() {
+ @Override
+ public void onConnected(ChildProcessConnection connection) {
+ assert mConnection == connection;
+ onServiceConnected();
+ }
+ };
+ Bundle connectionBundle = createConnectionBundle();
+ mDelegate.onBeforeConnectionSetup(connectionBundle);
+ mConnection.setupConnection(connectionBundle, getClientInterfaces(), connectionCallback);
+ }
+
+ private void onServiceConnected() {
+ assert isRunningOnLauncherThread();
+
+ Log.d(TAG, "on connect callback, pid=%d", mConnection.getPid());
+
+ mDelegate.onConnectionEstablished(mConnection);
+
+ // Proactively close the FDs rather than waiting for the GC to do it.
+ try {
+ for (FileDescriptorInfo fileInfo : mFilesToBeMapped) {
+ fileInfo.fd.close();
+ }
+ } catch (IOException ioe) {
+ Log.w(TAG, "Failed to close FD.", ioe);
+ }
+ }
+
+ public int getPid() {
+ assert isRunningOnLauncherThread();
+ return mConnection == null ? NULL_PROCESS_HANDLE : mConnection.getPid();
+ }
+
+ public List<IBinder> getClientInterfaces() {
+ return mClientInterfaces;
+ }
+
+ private boolean isRunningOnLauncherThread() {
+ return mLauncherHandler.getLooper() == Looper.myLooper();
+ }
+
+ private Bundle createConnectionBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE, mCommandLine);
+ bundle.putParcelableArray(ChildProcessConstants.EXTRA_FILES, mFilesToBeMapped);
+ return bundle;
+ }
+
+ private void onChildProcessDied() {
+ assert isRunningOnLauncherThread();
+ if (getPid() != 0) {
+ mDelegate.onConnectionLost(mConnection);
+ }
+ }
+
+ public void stop() {
+ assert isRunningOnLauncherThread();
+ Log.d(TAG, "stopping child connection: pid=%d", mConnection.getPid());
+ mConnection.stop();
+ }
+}