| Index: content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
|
| index 2d7419fed29d28efdeac53b736174e66774aa1f0..9234b594dfce7c1cf06de8880c8b37daa6f61657 100644
|
| --- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
|
| @@ -27,7 +27,9 @@ import org.chromium.content.common.IChildProcessCallback;
|
| import org.chromium.content.common.SurfaceWrapper;
|
|
|
| import java.util.ArrayList;
|
| +import java.util.LinkedList;
|
| import java.util.Map;
|
| +import java.util.Queue;
|
| import java.util.concurrent.ConcurrentHashMap;
|
|
|
| /**
|
| @@ -51,13 +53,7 @@ public class ChildProcessLauncher {
|
| // Connections to services. Indices of the array correspond to the service numbers.
|
| private final ChildProcessConnection[] mChildProcessConnections;
|
|
|
| - // The list of free (not bound) service indices. When looking for a free service, the first
|
| - // index in that list should be used. When a service is unbound, its index is added to the
|
| - // end of the list. This is so that we avoid immediately reusing the freed service (see
|
| - // http://crbug.com/164069): the framework might keep a service process alive when it's been
|
| - // unbound for a short time. If a new connection to the same service is bound at that point,
|
| - // the process is reused and bad things happen (mostly static variables are set when we
|
| - // don't expect them to).
|
| + // The list of free (not bound) service indices.
|
| // SHOULD BE ACCESSED WITH mConnectionLock.
|
| private final ArrayList<Integer> mFreeConnectionIndices;
|
| private final Object mConnectionLock = new Object();
|
| @@ -81,8 +77,7 @@ public class ChildProcessLauncher {
|
| ChromiumLinkerParams chromiumLinkerParams) {
|
| synchronized (mConnectionLock) {
|
| if (mFreeConnectionIndices.isEmpty()) {
|
| - Log.e(TAG, "Ran out of services to allocate.");
|
| - assert false;
|
| + Log.d(TAG, "Ran out of services to allocate.");
|
| return null;
|
| }
|
| int slot = mFreeConnectionIndices.remove(0);
|
| @@ -121,6 +116,91 @@ public class ChildProcessLauncher {
|
| }
|
| }
|
|
|
| + private static class PendingSpawnData {
|
| + private final Context mContext;
|
| + private final String[] mCommandLine;
|
| + private final int mChildProcessId;
|
| + private final FileDescriptorInfo[] mFilesToBeMapped;
|
| + private final long mClientContext;
|
| + private final int mCallbackType;
|
| + private final boolean mInSandbox;
|
| +
|
| + private PendingSpawnData(
|
| + Context context,
|
| + String[] commandLine,
|
| + int childProcessId,
|
| + FileDescriptorInfo[] filesToBeMapped,
|
| + long clientContext,
|
| + int callbackType,
|
| + boolean inSandbox) {
|
| + mContext = context;
|
| + mCommandLine = commandLine;
|
| + mChildProcessId = childProcessId;
|
| + mFilesToBeMapped = filesToBeMapped;
|
| + mClientContext = clientContext;
|
| + mCallbackType = callbackType;
|
| + mInSandbox = inSandbox;
|
| + }
|
| +
|
| + private Context context() {
|
| + return mContext;
|
| + }
|
| + private String[] commandLine() {
|
| + return mCommandLine;
|
| + }
|
| + private int childProcessId() {
|
| + return mChildProcessId;
|
| + }
|
| + private FileDescriptorInfo[] filesToBeMapped() {
|
| + return mFilesToBeMapped;
|
| + }
|
| + private long clientContext() {
|
| + return mClientContext;
|
| + }
|
| + private int callbackType() {
|
| + return mCallbackType;
|
| + }
|
| + private boolean inSandbox() {
|
| + return mInSandbox;
|
| + }
|
| + }
|
| +
|
| + private static class PendingSpawnQueue {
|
| + // The list of pending process spawn requests and its lock.
|
| + private static Queue<PendingSpawnData> sPendingSpawns =
|
| + new LinkedList<PendingSpawnData>();
|
| + static final Object sPendingSpawnsLock = new Object();
|
| +
|
| + /**
|
| + * Queue up a spawn requests to be processed once a free service is available.
|
| + * Called when a spawn is requested while we are at the capacity.
|
| + */
|
| + public void enqueue(final PendingSpawnData pendingSpawn) {
|
| + synchronized (sPendingSpawnsLock) {
|
| + sPendingSpawns.add(pendingSpawn);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Pop the next request from the queue. Called when a free service is available.
|
| + * @return the next spawn request waiting in the queue.
|
| + */
|
| + public PendingSpawnData dequeue() {
|
| + synchronized (sPendingSpawnsLock) {
|
| + return sPendingSpawns.poll();
|
| + }
|
| + }
|
| +
|
| + /** @return the count of pending spawns in the queue */
|
| + public int size() {
|
| + synchronized (sPendingSpawnsLock) {
|
| + return sPendingSpawns.size();
|
| + }
|
| + }
|
| + }
|
| +
|
| + private static final PendingSpawnQueue sPendingSpawnQueue = new PendingSpawnQueue();
|
| +
|
| // Service class for child process. As the default value it uses SandboxedProcessService0 and
|
| // PrivilegedProcessService0.
|
| private static ChildConnectionAllocator sSandboxedChildConnectionAllocator;
|
| @@ -218,8 +298,34 @@ public class ChildProcessLauncher {
|
| return connection;
|
| }
|
|
|
| + private static final long FREE_CONNECTION_DELAY_MILLIS = 1;
|
| +
|
| private static void freeConnection(ChildProcessConnection connection) {
|
| - getConnectionAllocator(connection.isInSandbox()).free(connection);
|
| + // Freeing a service should be delayed. This is so that we avoid immediately reusing the
|
| + // freed service (see http://crbug.com/164069): the framework might keep a service process
|
| + // alive when it's been unbound for a short time. If a new connection to the same service
|
| + // is bound at that point, the process is reused and bad things happen (mostly static
|
| + // variables are set when we don't expect them to).
|
| + final ChildProcessConnection conn = connection;
|
| + ThreadUtils.postOnUiThreadDelayed(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + getConnectionAllocator(conn.isInSandbox()).free(conn);
|
| +
|
| + final PendingSpawnData pendingSpawn = sPendingSpawnQueue.dequeue();
|
| + if (pendingSpawn != null) {
|
| + new Thread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + startInternal(pendingSpawn.context(), pendingSpawn.commandLine(),
|
| + pendingSpawn.childProcessId(), pendingSpawn.filesToBeMapped(),
|
| + pendingSpawn.clientContext(), pendingSpawn.callbackType(),
|
| + pendingSpawn.inSandbox());
|
| + }
|
| + }).start();
|
| + }
|
| + }
|
| + }, FREE_CONNECTION_DELAY_MILLIS);
|
| }
|
|
|
| // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
|
| @@ -387,30 +493,43 @@ public class ChildProcessLauncher {
|
| int[] fileFds,
|
| boolean[] fileAutoClose,
|
| long clientContext) {
|
| + assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length;
|
| + FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
|
| + for (int i = 0; i < fileFds.length; i++) {
|
| + filesToBeMapped[i] =
|
| + new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
|
| + }
|
| + assert clientContext != 0;
|
| +
|
| + int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
|
| + boolean inSandbox = true;
|
| + String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
|
| + if (SWITCH_RENDERER_PROCESS.equals(processType)) {
|
| + callbackType = CALLBACK_FOR_RENDERER_PROCESS;
|
| + } else if (SWITCH_GPU_PROCESS.equals(processType)) {
|
| + callbackType = CALLBACK_FOR_GPU_PROCESS;
|
| + inSandbox = false;
|
| + } else if (SWITCH_UTILITY_PROCESS.equals(processType)) {
|
| + // We only support sandboxed right now.
|
| + callbackType = CALLBACK_FOR_UTILITY_PROCESS;
|
| + } else {
|
| + assert false;
|
| + }
|
| +
|
| + startInternal(context, commandLine, childProcessId, filesToBeMapped, clientContext,
|
| + callbackType, inSandbox);
|
| + }
|
| +
|
| + private static void startInternal(
|
| + Context context,
|
| + final String[] commandLine,
|
| + int childProcessId,
|
| + FileDescriptorInfo[] filesToBeMapped,
|
| + long clientContext,
|
| + int callbackType,
|
| + boolean inSandbox) {
|
| try {
|
| - TraceEvent.begin("ChildProcessLauncher.start");
|
| - assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length;
|
| - FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
|
| - for (int i = 0; i < fileFds.length; i++) {
|
| - filesToBeMapped[i] =
|
| - new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
|
| - }
|
| - assert clientContext != 0;
|
| -
|
| - int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
|
| - boolean inSandbox = true;
|
| - String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
|
| - if (SWITCH_RENDERER_PROCESS.equals(processType)) {
|
| - callbackType = CALLBACK_FOR_RENDERER_PROCESS;
|
| - } else if (SWITCH_GPU_PROCESS.equals(processType)) {
|
| - callbackType = CALLBACK_FOR_GPU_PROCESS;
|
| - inSandbox = false;
|
| - } else if (SWITCH_UTILITY_PROCESS.equals(processType)) {
|
| - // We only support sandboxed right now.
|
| - callbackType = CALLBACK_FOR_UTILITY_PROCESS;
|
| - } else {
|
| - assert false;
|
| - }
|
| + TraceEvent.begin("ChildProcessLauncher.startInternal");
|
|
|
| ChildProcessConnection allocatedConnection = null;
|
| synchronized (ChildProcessLauncher.class) {
|
| @@ -422,9 +541,10 @@ public class ChildProcessLauncher {
|
| if (allocatedConnection == null) {
|
| allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox);
|
| if (allocatedConnection == null) {
|
| - // Notify the native code so it can free the heap allocated callback.
|
| - nativeOnChildProcessStarted(clientContext, 0);
|
| - Log.e(TAG, "Allocation of new service failed.");
|
| + Log.d(TAG, "Allocation of new service failed. Queuing up pending spawn.");
|
| + sPendingSpawnQueue.enqueue(new PendingSpawnData(context, commandLine,
|
| + childProcessId, filesToBeMapped, clientContext, callbackType,
|
| + inSandbox));
|
| return;
|
| }
|
| }
|
| @@ -434,7 +554,7 @@ public class ChildProcessLauncher {
|
| triggerConnectionSetup(allocatedConnection, commandLine, childProcessId,
|
| filesToBeMapped, callbackType, clientContext);
|
| } finally {
|
| - TraceEvent.end("ChildProcessLauncher.start");
|
| + TraceEvent.end("ChildProcessLauncher.startInternal");
|
| }
|
| }
|
|
|
| @@ -580,6 +700,15 @@ public class ChildProcessLauncher {
|
| return allocateBoundConnection(context, null, true);
|
| }
|
|
|
| + /**
|
| + * Queue up a spawn requests for testing.
|
| + */
|
| + @VisibleForTesting
|
| + static void enqueuePendingSpawnForTesting(Context context) {
|
| + sPendingSpawnQueue.enqueue(new PendingSpawnData(context, new String[0], 1,
|
| + new FileDescriptorInfo[0], 0, CALLBACK_FOR_RENDERER_PROCESS, true));
|
| + }
|
| +
|
| /** @return the count of sandboxed connections managed by the allocator */
|
| @VisibleForTesting
|
| static int allocatedConnectionsCountForTesting(Context context) {
|
| @@ -593,6 +722,12 @@ public class ChildProcessLauncher {
|
| return sServiceMap.size();
|
| }
|
|
|
| + /** @return the count of pending spawns in the queue */
|
| + @VisibleForTesting
|
| + static int pendingSpawnsCountForTesting() {
|
| + return sPendingSpawnQueue.size();
|
| + }
|
| +
|
| /**
|
| * Kills the child process for testing.
|
| * @return true iff the process was killed as expected
|
|
|