OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.content.browser; |
| 6 |
| 7 import android.content.ComponentName; |
| 8 import android.content.Context; |
| 9 import android.content.pm.ApplicationInfo; |
| 10 import android.content.pm.PackageManager; |
| 11 import android.os.Bundle; |
| 12 import android.text.TextUtils; |
| 13 |
| 14 import org.chromium.base.Log; |
| 15 import org.chromium.base.VisibleForTesting; |
| 16 import org.chromium.content.app.PrivilegedProcessService; |
| 17 import org.chromium.content.app.SandboxedProcessService; |
| 18 |
| 19 import java.util.ArrayList; |
| 20 import java.util.LinkedList; |
| 21 import java.util.Map; |
| 22 import java.util.Queue; |
| 23 import java.util.concurrent.ConcurrentHashMap; |
| 24 |
| 25 import javax.annotation.concurrent.GuardedBy; |
| 26 |
| 27 /** |
| 28 * This class is responsible for allocating and managing connections to child |
| 29 * process services. These connections are in a pool (the services are defined |
| 30 * in the AndroidManifest.xml). |
| 31 */ |
| 32 public class ChildConnectionAllocator { |
| 33 private static final String TAG = "ChildConnAllocator"; |
| 34 |
| 35 private static final String NUM_SANDBOXED_SERVICES_KEY = |
| 36 "org.chromium.content.browser.NUM_SANDBOXED_SERVICES"; |
| 37 private static final String NUM_PRIVILEGED_SERVICES_KEY = |
| 38 "org.chromium.content.browser.NUM_PRIVILEGED_SERVICES"; |
| 39 private static final String SANDBOXED_SERVICES_NAME_KEY = |
| 40 "org.chromium.content.browser.SANDBOXED_SERVICES_NAME"; |
| 41 |
| 42 private static final Object sAllocatorLock = new Object(); |
| 43 |
| 44 // Map from package name to ChildConnectionAllocator. |
| 45 @GuardedBy("sAllocatorLock") |
| 46 private static Map<String, ChildConnectionAllocator> sSandboxedChildConnecti
onAllocatorMap; |
| 47 |
| 48 // Allocator used for non-sandboxed services. |
| 49 @GuardedBy("sAllocatorLock") |
| 50 private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator; |
| 51 |
| 52 // Used by test to override the default sandboxed service settings. |
| 53 private static int sSandboxedServicesCountForTesting = -1; |
| 54 private static String sSandboxedServicesNameForTesting; |
| 55 |
| 56 // Connections to services. Indices of the array correspond to the service n
umbers. |
| 57 private final ChildProcessConnection[] mChildProcessConnections; |
| 58 |
| 59 private final String mChildClassName; |
| 60 private final boolean mInSandbox; |
| 61 |
| 62 private final Object mConnectionLock = new Object(); |
| 63 |
| 64 // The list of free (not bound) service indices. |
| 65 @GuardedBy("mConnectionLock") |
| 66 private final ArrayList<Integer> mFreeConnectionIndices; |
| 67 |
| 68 // Each Allocator keeps a queue for the pending spawn data. Once a connectio
n is free, we |
| 69 // dequeue the pending spawn data from the same allocator as the connection. |
| 70 @GuardedBy("mConnectionLock") |
| 71 private final Queue<ChildSpawnData> mPendingSpawnQueue = new LinkedList<>(); |
| 72 |
| 73 public static ChildConnectionAllocator getAllocator( |
| 74 Context context, String packageName, boolean inSandbox) { |
| 75 synchronized (sAllocatorLock) { |
| 76 if (!inSandbox) { |
| 77 if (sPrivilegedChildConnectionAllocator == null) { |
| 78 sPrivilegedChildConnectionAllocator = new ChildConnectionAll
ocator(false, |
| 79 getNumberOfServices(context, false, packageName), |
| 80 getClassNameOfService(context, false, packageName)); |
| 81 } |
| 82 return sPrivilegedChildConnectionAllocator; |
| 83 } |
| 84 |
| 85 if (sSandboxedChildConnectionAllocatorMap == null) { |
| 86 sSandboxedChildConnectionAllocatorMap = |
| 87 new ConcurrentHashMap<String, ChildConnectionAllocator>(
); |
| 88 } |
| 89 if (!sSandboxedChildConnectionAllocatorMap.containsKey(packageName))
{ |
| 90 Log.w(TAG, |
| 91 "Create a new ChildConnectionAllocator with package name
= %s," |
| 92 + " inSandbox = true", |
| 93 packageName); |
| 94 sSandboxedChildConnectionAllocatorMap.put(packageName, |
| 95 new ChildConnectionAllocator(true, |
| 96 getNumberOfServices(context, true, packageName), |
| 97 getClassNameOfService(context, true, packageName
))); |
| 98 } |
| 99 return sSandboxedChildConnectionAllocatorMap.get(packageName); |
| 100 // TODO(pkotwicz|hanxi): Figure out when old allocators should be re
moved from |
| 101 // {@code sSandboxedChildConnectionAllocatorMap}. |
| 102 } |
| 103 } |
| 104 |
| 105 private static String getClassNameOfService( |
| 106 Context context, boolean inSandbox, String packageName) { |
| 107 if (!inSandbox) { |
| 108 return PrivilegedProcessService.class.getName(); |
| 109 } |
| 110 |
| 111 if (!TextUtils.isEmpty(sSandboxedServicesNameForTesting)) { |
| 112 return sSandboxedServicesNameForTesting; |
| 113 } |
| 114 |
| 115 String serviceName = null; |
| 116 try { |
| 117 PackageManager packageManager = context.getPackageManager(); |
| 118 ApplicationInfo appInfo = |
| 119 packageManager.getApplicationInfo(packageName, PackageManage
r.GET_META_DATA); |
| 120 if (appInfo.metaData != null) { |
| 121 serviceName = appInfo.metaData.getString(SANDBOXED_SERVICES_NAME
_KEY); |
| 122 } |
| 123 } catch (PackageManager.NameNotFoundException e) { |
| 124 throw new RuntimeException("Could not get application info."); |
| 125 } |
| 126 |
| 127 if (serviceName != null) { |
| 128 // Check that the service exists. |
| 129 try { |
| 130 PackageManager packageManager = context.getPackageManager(); |
| 131 // PackageManager#getServiceInfo() throws an exception if the se
rvice does not |
| 132 // exist. |
| 133 packageManager.getServiceInfo(new ComponentName(packageName, ser
viceName + "0"), 0); |
| 134 return serviceName; |
| 135 } catch (PackageManager.NameNotFoundException e) { |
| 136 throw new RuntimeException( |
| 137 "Illegal meta data value: the child service doesn't exis
t"); |
| 138 } |
| 139 } |
| 140 return SandboxedProcessService.class.getName(); |
| 141 } |
| 142 |
| 143 static int getNumberOfServices(Context context, boolean inSandbox, String pa
ckageName) { |
| 144 int numServices = -1; |
| 145 if (inSandbox && sSandboxedServicesCountForTesting != -1) { |
| 146 numServices = sSandboxedServicesCountForTesting; |
| 147 } else { |
| 148 try { |
| 149 PackageManager packageManager = context.getPackageManager(); |
| 150 ApplicationInfo appInfo = packageManager.getApplicationInfo( |
| 151 packageName, PackageManager.GET_META_DATA); |
| 152 if (appInfo.metaData != null) { |
| 153 numServices = appInfo.metaData.getInt( |
| 154 inSandbox ? NUM_SANDBOXED_SERVICES_KEY : NUM_PRIVILE
GED_SERVICES_KEY, |
| 155 -1); |
| 156 } |
| 157 } catch (PackageManager.NameNotFoundException e) { |
| 158 throw new RuntimeException("Could not get application info"); |
| 159 } |
| 160 } |
| 161 if (numServices < 0) { |
| 162 throw new RuntimeException("Illegal meta data value for number of ch
ild services"); |
| 163 } |
| 164 return numServices; |
| 165 } |
| 166 |
| 167 @VisibleForTesting |
| 168 public static void setSanboxServicesSettingsForTesting(int serviceCount, Str
ing serviceName) { |
| 169 sSandboxedServicesCountForTesting = serviceCount; |
| 170 sSandboxedServicesNameForTesting = serviceName; |
| 171 } |
| 172 |
| 173 private ChildConnectionAllocator( |
| 174 boolean inSandbox, int numChildServices, String serviceClassName) { |
| 175 mChildProcessConnections = new ChildProcessConnectionImpl[numChildServic
es]; |
| 176 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); |
| 177 for (int i = 0; i < numChildServices; i++) { |
| 178 mFreeConnectionIndices.add(i); |
| 179 } |
| 180 mChildClassName = serviceClassName; |
| 181 mInSandbox = inSandbox; |
| 182 } |
| 183 |
| 184 // Allocates or enqueues. If there are no free slots, returns null and enque
ues the spawn data. |
| 185 public ChildProcessConnection allocate(ChildSpawnData spawnData, |
| 186 ChildProcessConnection.DeathCallback deathCallback, Bundle childProc
essCommonParameters, |
| 187 boolean queueIfNoSlotAvailable) { |
| 188 assert spawnData.isInSandbox() == mInSandbox; |
| 189 synchronized (mConnectionLock) { |
| 190 if (mFreeConnectionIndices.isEmpty()) { |
| 191 Log.d(TAG, "Ran out of services to allocate."); |
| 192 if (queueIfNoSlotAvailable) { |
| 193 mPendingSpawnQueue.add(spawnData); |
| 194 } |
| 195 return null; |
| 196 } |
| 197 int slot = mFreeConnectionIndices.remove(0); |
| 198 assert mChildProcessConnections[slot] == null; |
| 199 mChildProcessConnections[slot] = new ChildProcessConnectionImpl(spaw
nData.getContext(), |
| 200 slot, mInSandbox, deathCallback, mChildClassName, childProce
ssCommonParameters, |
| 201 spawnData.isAlwaysInForeground(), spawnData.getCreationParam
s()); |
| 202 Log.d(TAG, "Allocator allocated a connection, sandbox: %b, slot: %d"
, mInSandbox, slot); |
| 203 return mChildProcessConnections[slot]; |
| 204 } |
| 205 } |
| 206 |
| 207 // Also return the first ChildSpawnData in the pending queue, if any. |
| 208 public ChildSpawnData free(ChildProcessConnection connection) { |
| 209 synchronized (mConnectionLock) { |
| 210 int slot = connection.getServiceNumber(); |
| 211 if (mChildProcessConnections[slot] != connection) { |
| 212 int occupier = mChildProcessConnections[slot] == null |
| 213 ? -1 |
| 214 : mChildProcessConnections[slot].getServiceNumber(); |
| 215 Log.e(TAG, |
| 216 "Unable to find connection to free in slot: %d " |
| 217 + "already occupied by service: %d", |
| 218 slot, occupier); |
| 219 assert false; |
| 220 } else { |
| 221 mChildProcessConnections[slot] = null; |
| 222 assert !mFreeConnectionIndices.contains(slot); |
| 223 mFreeConnectionIndices.add(slot); |
| 224 Log.d(TAG, "Allocator freed a connection, sandbox: %b, slot: %d"
, mInSandbox, slot); |
| 225 } |
| 226 return mPendingSpawnQueue.poll(); |
| 227 } |
| 228 } |
| 229 |
| 230 public boolean isFreeConnectionAvailable() { |
| 231 synchronized (mConnectionLock) { |
| 232 return !mFreeConnectionIndices.isEmpty(); |
| 233 } |
| 234 } |
| 235 |
| 236 /** @return the count of connections managed by the allocator */ |
| 237 @VisibleForTesting |
| 238 int allocatedConnectionsCountForTesting() { |
| 239 synchronized (mConnectionLock) { |
| 240 return mChildProcessConnections.length - mFreeConnectionIndices.size
(); |
| 241 } |
| 242 } |
| 243 |
| 244 @VisibleForTesting |
| 245 ChildProcessConnection[] connectionArrayForTesting() { |
| 246 return mChildProcessConnections; |
| 247 } |
| 248 |
| 249 @VisibleForTesting |
| 250 void enqueuePendingQueueForTesting(ChildSpawnData spawnData) { |
| 251 synchronized (mConnectionLock) { |
| 252 mPendingSpawnQueue.add(spawnData); |
| 253 } |
| 254 } |
| 255 |
| 256 @VisibleForTesting |
| 257 int pendingSpawnsCountForTesting() { |
| 258 synchronized (mConnectionLock) { |
| 259 return mPendingSpawnQueue.size(); |
| 260 } |
| 261 } |
| 262 } |
OLD | NEW |