Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(733)

Unified Diff: content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java

Issue 704573002: Android: Use a spawning queue for child processes while we are at capacity. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix for assert message. Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « no previous file | content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698