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 |