| 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..25872326fa838d1cbfa6e7fb4de6292205c75bb3
|
| --- /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";
|
| +
|
| + private static final Object sAllocatorLock = new Object();
|
| +
|
| + // Map from package name to ChildConnectionAllocator.
|
| + @GuardedBy("sAllocatorLock")
|
| + private static Map<String, ChildConnectionAllocator> sSandboxedChildConnectionAllocatorMap;
|
| +
|
| + // Allocator used for non-sandboxed services.
|
| + @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;
|
| +
|
| + // 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<>();
|
| +
|
| + 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}.
|
| + }
|
| + }
|
| +
|
| + private static String getClassNameOfService(
|
| + 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;
|
| + }
|
| +
|
| + @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();
|
| + }
|
| + }
|
| +
|
| + /** @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();
|
| + }
|
| + }
|
| +}
|
|
|