Chromium Code Reviews| Index: content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java |
| diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java b/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..055e82792d099510e2c9c43823cef1b8fd8ce515 |
| --- /dev/null |
| +++ b/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java |
| @@ -0,0 +1,262 @@ |
| +// 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.content.browser; |
| + |
| +import android.content.ComponentName; |
| +import android.content.Context; |
| +import android.content.pm.ApplicationInfo; |
| +import android.content.pm.PackageManager; |
| +import android.os.Bundle; |
| +import android.text.TextUtils; |
| + |
| +import org.chromium.base.Log; |
| +import org.chromium.base.VisibleForTesting; |
| +import org.chromium.content.app.PrivilegedProcessService; |
| +import org.chromium.content.app.SandboxedProcessService; |
| + |
| +import java.util.ArrayList; |
| +import java.util.LinkedList; |
| +import java.util.Map; |
| +import java.util.Queue; |
| +import java.util.concurrent.ConcurrentHashMap; |
| + |
| +import javax.annotation.concurrent.GuardedBy; |
| + |
| +/** |
| + * This class is responsible for allocating and managing connections to child |
| + * process services. These connections are in a pool (the services are defined |
| + * in the AndroidManifest.xml). |
| + */ |
| +public class ChildConnectionAllocator { |
| + private static final String TAG = "ChildConnAllocator"; |
| + |
| + private static final String NUM_SANDBOXED_SERVICES_KEY = |
| + "org.chromium.content.browser.NUM_SANDBOXED_SERVICES"; |
| + private static final String NUM_PRIVILEGED_SERVICES_KEY = |
| + "org.chromium.content.browser.NUM_PRIVILEGED_SERVICES"; |
| + private static final String SANDBOXED_SERVICES_NAME_KEY = |
| + "org.chromium.content.browser.SANDBOXED_SERVICES_NAME"; |
| + |
| + // Connections to services. Indices of the array correspond to the service numbers. |
| + private final ChildProcessConnection[] mChildProcessConnections; |
| + |
| + private final String mChildClassName; |
| + private final boolean mInSandbox; |
| + |
| + private final Object mConnectionLock = new Object(); |
| + |
| + // The list of free (not bound) service indices. |
| + @GuardedBy("mConnectionLock") |
| + private final ArrayList<Integer> mFreeConnectionIndices; |
| + |
| + // Each Allocator keeps a queue for the pending spawn data. Once a connection is free, we |
| + // dequeue the pending spawn data from the same allocator as the connection. |
| + @GuardedBy("mConnectionLock") |
| + private final Queue<ChildSpawnData> mPendingSpawnQueue = new LinkedList<>(); |
| + |
| + private static final Object sAllocatorLock = new Object(); |
|
boliu
2017/04/12 00:20:16
nit: move static vars above instance vars, but bel
Jay Civelli
2017/04/12 00:47:15
Done.
|
| + |
| + // Map from package name to ChildConnectionAllocator. |
| + @GuardedBy("sAllocatorLock") |
| + private static Map<String, ChildConnectionAllocator> sSandboxedChildConnectionAllocatorMap; |
| + |
| + // As the default value it uses PrivilegedProcessService0. |
|
boliu
2017/04/12 00:20:15
nit: this comment makes no sense to me..
Jay Civelli
2017/04/12 00:47:15
Me neither, I had just moved it from its original
|
| + @GuardedBy("sAllocatorLock") |
| + private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator; |
| + |
| + // Used by test to override the default sandboxed service settings. |
| + private static int sSandboxedServicesCountForTesting = -1; |
| + private static String sSandboxedServicesNameForTesting; |
| + |
| + public static ChildConnectionAllocator getAllocator( |
| + Context context, String packageName, boolean inSandbox) { |
| + synchronized (sAllocatorLock) { |
| + if (!inSandbox) { |
| + if (sPrivilegedChildConnectionAllocator == null) { |
| + sPrivilegedChildConnectionAllocator = new ChildConnectionAllocator(false, |
| + getNumberOfServices(context, false, packageName), |
| + getClassNameOfService(context, false, packageName)); |
| + } |
| + return sPrivilegedChildConnectionAllocator; |
| + } |
| + |
| + if (sSandboxedChildConnectionAllocatorMap == null) { |
| + sSandboxedChildConnectionAllocatorMap = |
| + new ConcurrentHashMap<String, ChildConnectionAllocator>(); |
| + } |
| + if (!sSandboxedChildConnectionAllocatorMap.containsKey(packageName)) { |
| + Log.w(TAG, |
| + "Create a new ChildConnectionAllocator with package name = %s," |
| + + " inSandbox = true", |
| + packageName); |
| + sSandboxedChildConnectionAllocatorMap.put(packageName, |
| + new ChildConnectionAllocator(true, |
| + getNumberOfServices(context, true, packageName), |
| + getClassNameOfService(context, true, packageName))); |
| + } |
| + return sSandboxedChildConnectionAllocatorMap.get(packageName); |
| + // TODO(pkotwicz|hanxi): Figure out when old allocators should be removed from |
| + // {@code sSandboxedChildConnectionAllocatorMap}. |
| + } |
| + } |
| + |
| + @VisibleForTesting |
| + public static void setSanboxServicesSettingsForTesting(int serviceCount, String serviceName) { |
| + sSandboxedServicesCountForTesting = serviceCount; |
| + sSandboxedServicesNameForTesting = serviceName; |
| + } |
| + |
| + private ChildConnectionAllocator( |
| + boolean inSandbox, int numChildServices, String serviceClassName) { |
| + mChildProcessConnections = new ChildProcessConnectionImpl[numChildServices]; |
| + mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); |
| + for (int i = 0; i < numChildServices; i++) { |
| + mFreeConnectionIndices.add(i); |
| + } |
| + mChildClassName = serviceClassName; |
| + mInSandbox = inSandbox; |
| + } |
| + |
| + // Allocates or enqueues. If there are no free slots, returns null and enqueues the spawn data. |
| + public ChildProcessConnection allocate(ChildSpawnData spawnData, |
| + ChildProcessConnection.DeathCallback deathCallback, Bundle childProcessCommonParameters, |
| + boolean queueIfNoSlotAvailable) { |
| + assert spawnData.isInSandbox() == mInSandbox; |
| + synchronized (mConnectionLock) { |
| + if (mFreeConnectionIndices.isEmpty()) { |
| + Log.d(TAG, "Ran out of services to allocate."); |
| + if (queueIfNoSlotAvailable) { |
| + mPendingSpawnQueue.add(spawnData); |
| + } |
| + return null; |
| + } |
| + int slot = mFreeConnectionIndices.remove(0); |
| + assert mChildProcessConnections[slot] == null; |
| + mChildProcessConnections[slot] = new ChildProcessConnectionImpl(spawnData.getContext(), |
| + slot, mInSandbox, deathCallback, mChildClassName, childProcessCommonParameters, |
| + spawnData.isAlwaysInForeground(), spawnData.getCreationParams()); |
| + Log.d(TAG, "Allocator allocated a connection, sandbox: %b, slot: %d", mInSandbox, slot); |
| + return mChildProcessConnections[slot]; |
| + } |
| + } |
| + |
| + // Also return the first ChildSpawnData in the pending queue, if any. |
| + public ChildSpawnData free(ChildProcessConnection connection) { |
| + synchronized (mConnectionLock) { |
| + int slot = connection.getServiceNumber(); |
| + if (mChildProcessConnections[slot] != connection) { |
| + int occupier = mChildProcessConnections[slot] == null |
| + ? -1 |
| + : mChildProcessConnections[slot].getServiceNumber(); |
| + Log.e(TAG, |
| + "Unable to find connection to free in slot: %d " |
| + + "already occupied by service: %d", |
| + slot, occupier); |
| + assert false; |
| + } else { |
| + mChildProcessConnections[slot] = null; |
| + assert !mFreeConnectionIndices.contains(slot); |
| + mFreeConnectionIndices.add(slot); |
| + Log.d(TAG, "Allocator freed a connection, sandbox: %b, slot: %d", mInSandbox, slot); |
| + } |
| + return mPendingSpawnQueue.poll(); |
| + } |
| + } |
| + |
| + public boolean isFreeConnectionAvailable() { |
| + synchronized (mConnectionLock) { |
| + return !mFreeConnectionIndices.isEmpty(); |
| + } |
| + } |
| + |
| + private static String getClassNameOfService( |
|
boliu
2017/04/12 00:20:15
nit: same, group static methods together, perhaps
Jay Civelli
2017/04/12 00:47:15
Moved them up.
|
| + Context context, boolean inSandbox, String packageName) { |
| + if (!inSandbox) { |
| + return PrivilegedProcessService.class.getName(); |
| + } |
| + |
| + if (!TextUtils.isEmpty(sSandboxedServicesNameForTesting)) { |
| + return sSandboxedServicesNameForTesting; |
| + } |
| + |
| + String serviceName = null; |
| + try { |
| + PackageManager packageManager = context.getPackageManager(); |
| + ApplicationInfo appInfo = |
| + packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA); |
| + if (appInfo.metaData != null) { |
| + serviceName = appInfo.metaData.getString(SANDBOXED_SERVICES_NAME_KEY); |
| + } |
| + } catch (PackageManager.NameNotFoundException e) { |
| + throw new RuntimeException("Could not get application info."); |
| + } |
| + |
| + if (serviceName != null) { |
| + // Check that the service exists. |
| + try { |
| + PackageManager packageManager = context.getPackageManager(); |
| + // PackageManager#getServiceInfo() throws an exception if the service does not |
| + // exist. |
| + packageManager.getServiceInfo(new ComponentName(packageName, serviceName + "0"), 0); |
| + return serviceName; |
| + } catch (PackageManager.NameNotFoundException e) { |
| + throw new RuntimeException( |
| + "Illegal meta data value: the child service doesn't exist"); |
| + } |
| + } |
| + return SandboxedProcessService.class.getName(); |
| + } |
| + |
| + static int getNumberOfServices(Context context, boolean inSandbox, String packageName) { |
| + int numServices = -1; |
| + if (inSandbox && sSandboxedServicesCountForTesting != -1) { |
| + numServices = sSandboxedServicesCountForTesting; |
| + } else { |
| + try { |
| + PackageManager packageManager = context.getPackageManager(); |
| + ApplicationInfo appInfo = packageManager.getApplicationInfo( |
| + packageName, PackageManager.GET_META_DATA); |
| + if (appInfo.metaData != null) { |
| + numServices = appInfo.metaData.getInt( |
| + inSandbox ? NUM_SANDBOXED_SERVICES_KEY : NUM_PRIVILEGED_SERVICES_KEY, |
| + -1); |
| + } |
| + } catch (PackageManager.NameNotFoundException e) { |
| + throw new RuntimeException("Could not get application info"); |
| + } |
| + } |
| + if (numServices < 0) { |
| + throw new RuntimeException("Illegal meta data value for number of child services"); |
| + } |
| + return numServices; |
| + } |
| + |
| + /** @return the count of connections managed by the allocator */ |
| + @VisibleForTesting |
| + int allocatedConnectionsCountForTesting() { |
| + synchronized (mConnectionLock) { |
| + return mChildProcessConnections.length - mFreeConnectionIndices.size(); |
| + } |
| + } |
| + |
| + @VisibleForTesting |
| + ChildProcessConnection[] connectionArrayForTesting() { |
| + return mChildProcessConnections; |
| + } |
| + |
| + @VisibleForTesting |
| + void enqueuePendingQueueForTesting(ChildSpawnData spawnData) { |
| + synchronized (mConnectionLock) { |
| + mPendingSpawnQueue.add(spawnData); |
| + } |
| + } |
| + |
| + @VisibleForTesting |
| + int pendingSpawnsCountForTesting() { |
| + synchronized (mConnectionLock) { |
| + return mPendingSpawnQueue.size(); |
| + } |
| + } |
| +} |