Chromium Code Reviews| 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 // Connections to services. Indices of the array correspond to the service n umbers. | |
| 43 private final ChildProcessConnection[] mChildProcessConnections; | |
| 44 | |
| 45 private final String mChildClassName; | |
| 46 private final boolean mInSandbox; | |
| 47 | |
| 48 private final Object mConnectionLock = new Object(); | |
| 49 | |
| 50 // The list of free (not bound) service indices. | |
| 51 @GuardedBy("mConnectionLock") | |
| 52 private final ArrayList<Integer> mFreeConnectionIndices; | |
| 53 | |
| 54 // Each Allocator keeps a queue for the pending spawn data. Once a connectio n is free, we | |
| 55 // dequeue the pending spawn data from the same allocator as the connection. | |
| 56 @GuardedBy("mConnectionLock") | |
| 57 private final Queue<ChildSpawnData> mPendingSpawnQueue = new LinkedList<>(); | |
| 58 | |
| 59 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.
| |
| 60 | |
| 61 // Map from package name to ChildConnectionAllocator. | |
| 62 @GuardedBy("sAllocatorLock") | |
| 63 private static Map<String, ChildConnectionAllocator> sSandboxedChildConnecti onAllocatorMap; | |
| 64 | |
| 65 // 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
| |
| 66 @GuardedBy("sAllocatorLock") | |
| 67 private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator; | |
| 68 | |
| 69 // Used by test to override the default sandboxed service settings. | |
| 70 private static int sSandboxedServicesCountForTesting = -1; | |
| 71 private static String sSandboxedServicesNameForTesting; | |
| 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 @VisibleForTesting | |
| 106 public static void setSanboxServicesSettingsForTesting(int serviceCount, Str ing serviceName) { | |
| 107 sSandboxedServicesCountForTesting = serviceCount; | |
| 108 sSandboxedServicesNameForTesting = serviceName; | |
| 109 } | |
| 110 | |
| 111 private ChildConnectionAllocator( | |
| 112 boolean inSandbox, int numChildServices, String serviceClassName) { | |
| 113 mChildProcessConnections = new ChildProcessConnectionImpl[numChildServic es]; | |
| 114 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); | |
| 115 for (int i = 0; i < numChildServices; i++) { | |
| 116 mFreeConnectionIndices.add(i); | |
| 117 } | |
| 118 mChildClassName = serviceClassName; | |
| 119 mInSandbox = inSandbox; | |
| 120 } | |
| 121 | |
| 122 // Allocates or enqueues. If there are no free slots, returns null and enque ues the spawn data. | |
| 123 public ChildProcessConnection allocate(ChildSpawnData spawnData, | |
| 124 ChildProcessConnection.DeathCallback deathCallback, Bundle childProc essCommonParameters, | |
| 125 boolean queueIfNoSlotAvailable) { | |
| 126 assert spawnData.isInSandbox() == mInSandbox; | |
| 127 synchronized (mConnectionLock) { | |
| 128 if (mFreeConnectionIndices.isEmpty()) { | |
| 129 Log.d(TAG, "Ran out of services to allocate."); | |
| 130 if (queueIfNoSlotAvailable) { | |
| 131 mPendingSpawnQueue.add(spawnData); | |
| 132 } | |
| 133 return null; | |
| 134 } | |
| 135 int slot = mFreeConnectionIndices.remove(0); | |
| 136 assert mChildProcessConnections[slot] == null; | |
| 137 mChildProcessConnections[slot] = new ChildProcessConnectionImpl(spaw nData.getContext(), | |
| 138 slot, mInSandbox, deathCallback, mChildClassName, childProce ssCommonParameters, | |
| 139 spawnData.isAlwaysInForeground(), spawnData.getCreationParam s()); | |
| 140 Log.d(TAG, "Allocator allocated a connection, sandbox: %b, slot: %d" , mInSandbox, slot); | |
| 141 return mChildProcessConnections[slot]; | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 // Also return the first ChildSpawnData in the pending queue, if any. | |
| 146 public ChildSpawnData free(ChildProcessConnection connection) { | |
| 147 synchronized (mConnectionLock) { | |
| 148 int slot = connection.getServiceNumber(); | |
| 149 if (mChildProcessConnections[slot] != connection) { | |
| 150 int occupier = mChildProcessConnections[slot] == null | |
| 151 ? -1 | |
| 152 : mChildProcessConnections[slot].getServiceNumber(); | |
| 153 Log.e(TAG, | |
| 154 "Unable to find connection to free in slot: %d " | |
| 155 + "already occupied by service: %d", | |
| 156 slot, occupier); | |
| 157 assert false; | |
| 158 } else { | |
| 159 mChildProcessConnections[slot] = null; | |
| 160 assert !mFreeConnectionIndices.contains(slot); | |
| 161 mFreeConnectionIndices.add(slot); | |
| 162 Log.d(TAG, "Allocator freed a connection, sandbox: %b, slot: %d" , mInSandbox, slot); | |
| 163 } | |
| 164 return mPendingSpawnQueue.poll(); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 public boolean isFreeConnectionAvailable() { | |
| 169 synchronized (mConnectionLock) { | |
| 170 return !mFreeConnectionIndices.isEmpty(); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 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.
| |
| 175 Context context, boolean inSandbox, String packageName) { | |
| 176 if (!inSandbox) { | |
| 177 return PrivilegedProcessService.class.getName(); | |
| 178 } | |
| 179 | |
| 180 if (!TextUtils.isEmpty(sSandboxedServicesNameForTesting)) { | |
| 181 return sSandboxedServicesNameForTesting; | |
| 182 } | |
| 183 | |
| 184 String serviceName = null; | |
| 185 try { | |
| 186 PackageManager packageManager = context.getPackageManager(); | |
| 187 ApplicationInfo appInfo = | |
| 188 packageManager.getApplicationInfo(packageName, PackageManage r.GET_META_DATA); | |
| 189 if (appInfo.metaData != null) { | |
| 190 serviceName = appInfo.metaData.getString(SANDBOXED_SERVICES_NAME _KEY); | |
| 191 } | |
| 192 } catch (PackageManager.NameNotFoundException e) { | |
| 193 throw new RuntimeException("Could not get application info."); | |
| 194 } | |
| 195 | |
| 196 if (serviceName != null) { | |
| 197 // Check that the service exists. | |
| 198 try { | |
| 199 PackageManager packageManager = context.getPackageManager(); | |
| 200 // PackageManager#getServiceInfo() throws an exception if the se rvice does not | |
| 201 // exist. | |
| 202 packageManager.getServiceInfo(new ComponentName(packageName, ser viceName + "0"), 0); | |
| 203 return serviceName; | |
| 204 } catch (PackageManager.NameNotFoundException e) { | |
| 205 throw new RuntimeException( | |
| 206 "Illegal meta data value: the child service doesn't exis t"); | |
| 207 } | |
| 208 } | |
| 209 return SandboxedProcessService.class.getName(); | |
| 210 } | |
| 211 | |
| 212 static int getNumberOfServices(Context context, boolean inSandbox, String pa ckageName) { | |
| 213 int numServices = -1; | |
| 214 if (inSandbox && sSandboxedServicesCountForTesting != -1) { | |
| 215 numServices = sSandboxedServicesCountForTesting; | |
| 216 } else { | |
| 217 try { | |
| 218 PackageManager packageManager = context.getPackageManager(); | |
| 219 ApplicationInfo appInfo = packageManager.getApplicationInfo( | |
| 220 packageName, PackageManager.GET_META_DATA); | |
| 221 if (appInfo.metaData != null) { | |
| 222 numServices = appInfo.metaData.getInt( | |
| 223 inSandbox ? NUM_SANDBOXED_SERVICES_KEY : NUM_PRIVILE GED_SERVICES_KEY, | |
| 224 -1); | |
| 225 } | |
| 226 } catch (PackageManager.NameNotFoundException e) { | |
| 227 throw new RuntimeException("Could not get application info"); | |
| 228 } | |
| 229 } | |
| 230 if (numServices < 0) { | |
| 231 throw new RuntimeException("Illegal meta data value for number of ch ild services"); | |
| 232 } | |
| 233 return numServices; | |
| 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 |