1 // Copyright 2012 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.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.util.Log;
9 import android.util.SparseIntArray;
10 import android.view.Surface;
12 import java.util.ArrayList;
14 import java.util.concurrent.ConcurrentHashMap;
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.base.SysUtils;
19 import org.chromium.base.ThreadUtils;
20 import org.chromium.content.app.ChildProcessService;
21 import org.chromium.content.app.Linker;
22 import org.chromium.content.app.LinkerParams;
23 import org.chromium.content.app.PrivilegedProcessService;
24 import org.chromium.content.app.SandboxedProcessService;
25 import org.chromium.content.common.IChildProcessCallback;
26 import org.chromium.content.common.IChildProcessService;
29 * This class provides the method to start/stop ChildProcess called by native.
31 @JNINamespace("content")
32 public class ChildProcessLauncher {
33 private static String TAG = "ChildProcessLauncher";
35 private static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0;
36 private static final int CALLBACK_FOR_GPU_PROCESS = 1;
37 private static final int CALLBACK_FOR_RENDERER_PROCESS = 2;
39 private static final String SWITCH_PROCESS_TYPE = "type";
40 private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker";
41 private static final String SWITCH_RENDERER_PROCESS = "renderer";
42 private static final String SWITCH_GPU_PROCESS = "gpu-process";
44 // The upper limit on the number of simultaneous sandboxed and privileged child service process
45 // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX
46 // classes and PrivilegedProcessServiceX classes declared in this package and defined as
47 // services in the embedding application's manifest file.
48 // (See {@link ChildProcessService} for more details on defining the services.)
49 /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 13;
50 /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3;
52 private static class ChildConnectionAllocator {
53 // Connections to services. Indices of the array correspond to the service numbers.
54 private ChildProcessConnection[] mChildProcessConnections;
56 // The list of free (not bound) service indices. When looking for a free service, the first
57 // index in that list should be used. When a service is unbound, its index is added to the
58 // end of the list. This is so that we avoid immediately reusing the freed service (see
59 // http://crbug.com/164069): the framework might keep a service process alive when it's been
60 // unbound for a short time. If a new connection to the same service is bound at that point,
61 // the process is reused and bad things happen (mostly static variables are set when we
62 // don't expect them to).
63 // SHOULD BE ACCESSED WITH mConnectionLock.
64 private ArrayList<Integer> mFreeConnectionIndices;
65 private final Object mConnectionLock = new Object();
67 private Class<? extends ChildProcessService> mChildClass;
68 private final boolean mInSandbox;
70 public ChildConnectionAllocator(boolean inSandbox) {
71 int numChildServices = inSandbox ?
72 MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES;
73 mChildProcessConnections = new ChildProcessConnection[numChildServices];
74 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
75 for (int i = 0; i < numChildServices; i++) {
76 mFreeConnectionIndices.add(i);
78 setServiceClass(inSandbox ?
79 SandboxedProcessService.class : PrivilegedProcessService.class);
80 mInSandbox = inSandbox;
83 public void setServiceClass(Class<? extends ChildProcessService> childClass) {
84 mChildClass = childClass;
87 public ChildProcessConnection allocate(
88 Context context, ChildProcessConnection.DeathCallback deathCallback,
89 LinkerParams linkerParams) {
90 synchronized(mConnectionLock) {
91 if (mFreeConnectionIndices.isEmpty()) {
92 Log.w(TAG, "Ran out of service." );
95 int slot = mFreeConnectionIndices.remove(0);
96 assert mChildProcessConnections[slot] == null;
97 mChildProcessConnections[slot] = new ChildProcessConnection(context, slot,
98 mInSandbox, deathCallback, mChildClass, linkerParams);
99 return mChildProcessConnections[slot];
103 public void free(ChildProcessConnection connection) {
104 synchronized(mConnectionLock) {
105 int slot = connection.getServiceNumber();
106 if (mChildProcessConnections[slot] != connection) {
107 int occupier = mChildProcessConnections[slot] == null ?
108 -1 : mChildProcessConnections[slot].getServiceNumber();
109 Log.e(TAG, "Unable to find connection to free in slot: " + slot +
110 " already occupied by service: " + occupier);
113 mChildProcessConnections[slot] = null;
114 assert !mFreeConnectionIndices.contains(slot);
115 mFreeConnectionIndices.add(slot);
121 // Service class for child process. As the default value it uses SandboxedProcessService0 and
122 // PrivilegedProcessService0.
123 private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator =
124 new ChildConnectionAllocator(true);
125 private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator =
126 new ChildConnectionAllocator(false);
128 private static boolean sConnectionAllocated = false;
130 // Sets service class for sandboxed service and privileged service.
131 public static void setChildProcessClass(
132 Class<? extends SandboxedProcessService> sandboxedServiceClass,
133 Class<? extends PrivilegedProcessService> privilegedServiceClass) {
134 // We should guarantee this is called before allocating connection.
135 assert !sConnectionAllocated;
136 sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass);
137 sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass);
140 private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) {
142 sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator;
145 private static ChildProcessConnection allocateConnection(Context context,
146 boolean inSandbox, LinkerParams linkerParams) {
147 ChildProcessConnection.DeathCallback deathCallback =
148 new ChildProcessConnection.DeathCallback() {
150 public void onChildProcessDied(int pid) {
154 sConnectionAllocated = true;
155 return getConnectionAllocator(inSandbox).allocate(context, deathCallback, linkerParams);
158 private static boolean sLinkerInitialized = false;
159 private static long sLinkerLoadAddress = 0;
161 private static LinkerParams getLinkerParamsForNewConnection() {
162 if (!sLinkerInitialized) {
163 if (Linker.isUsed()) {
164 sLinkerLoadAddress = Linker.getBaseLoadAddress();
165 if (sLinkerLoadAddress == 0) {
166 Log.i(TAG, "Shared RELRO support disabled!");
169 sLinkerInitialized = true;
172 if (sLinkerLoadAddress == 0)
175 // Always wait for the shared RELROs in service processes.
176 final boolean waitForSharedRelros = true;
177 return new LinkerParams(sLinkerLoadAddress,
179 Linker.getTestRunnerClassName());
182 private static ChildProcessConnection allocateBoundConnection(Context context,
183 String[] commandLine, boolean inSandbox) {
184 LinkerParams linkerParams = getLinkerParamsForNewConnection();
185 ChildProcessConnection connection = allocateConnection(context, inSandbox, linkerParams);
186 if (connection != null) {
187 connection.start(commandLine);
192 private static void freeConnection(ChildProcessConnection connection) {
193 if (connection == null) {
196 getConnectionAllocator(connection.isInSandbox()).free(connection);
200 // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
201 private static final int NULL_PROCESS_HANDLE = 0;
203 // Map from pid to ChildService connection.
204 private static Map<Integer, ChildProcessConnection> sServiceMap =
205 new ConcurrentHashMap<Integer, ChildProcessConnection>();
207 // A pre-allocated and pre-bound connection ready for connection setup, or null.
208 private static ChildProcessConnection sSpareSandboxedConnection = null;
211 * Manages oom bindings used to bound child services. "Oom binding" is a binding that raises the
212 * process oom priority so that it shouldn't be killed by the OS out-of-memory killer under
213 * normal conditions (it can still be killed under drastic memory pressure).
215 * This class serves a proxy between external calls that manipulate the bindings and the
216 * connections, allowing to enforce policies such as delayed removal of the bindings.
218 static class BindingManager {
219 // Delay of 1 second used when removing the initial oom binding of a process.
220 private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000;
222 // Delay of 5 second used when removing temporary strong binding of a process (only on
223 // non-low-memory devices).
224 private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 5 * 1000;
226 // Map from pid to the count of oom bindings bound for the service. Should be accessed with
228 private final SparseIntArray mOomBindingCount = new SparseIntArray();
230 // Pid of the renderer that was most recently oom bound. This is used on low-memory devices
231 // to drop oom bindings of a process when another one acquires them, making sure that only
232 // one renderer process at a time is oom bound. Should be accessed with mCountLock.
233 private int mLastOomPid = -1;
235 // Should be acquired before binding or unbinding the connections and modifying state
236 // variables: mOomBindingCount and mLastOomPid.
237 private final Object mCountLock = new Object();
240 * Registers an oom binding bound for a child process. Should be called with mCountLock.
241 * @param pid handle of the process.
243 private void incrementOomCount(int pid) {
244 mOomBindingCount.put(pid, mOomBindingCount.get(pid) + 1);
249 * Registers an oom binding unbound for a child process. Should be called with mCountLock.
250 * @param pid handle of the process.
252 private void decrementOomCount(int pid) {
253 int count = mOomBindingCount.get(pid, -1);
257 mOomBindingCount.put(pid, count);
259 mOomBindingCount.delete(pid);
264 * Drops all oom bindings for the given renderer.
265 * @param pid handle of the process.
267 private void dropOomBindings(int pid) {
268 ChildProcessConnection connection = sServiceMap.get(pid);
269 if (connection == null) {
270 LogPidWarning(pid, "Tried to drop oom bindings for a non-existent connection");
273 synchronized (mCountLock) {
274 connection.dropOomBindings();
275 mOomBindingCount.delete(pid);
280 * Registers a freshly started child process. On low-memory devices this will also drop the
281 * oom bindings of the last process that was oom-bound. We can do that, because every time a
282 * connection is created on the low-end, it is used in foreground (no prerendering, no
283 * loading of tabs opened in background).
284 * @param pid handle of the process.
286 void addNewConnection(int pid) {
287 synchronized (mCountLock) {
288 if (SysUtils.isLowEndDevice() && mLastOomPid >= 0) {
289 dropOomBindings(mLastOomPid);
291 // This will reset the previous entry for the pid in the unlikely event of the OS
292 // reusing renderer pids.
293 mOomBindingCount.put(pid, 0);
294 // Every new connection is bound with initial oom binding.
295 incrementOomCount(pid);
300 * Remove the initial binding of the child process. Child processes are bound with initial
301 * binding to protect them from getting killed before they are put to use. This method
302 * allows to remove the binding once it is no longer needed. The binding is removed after a
303 * fixed delay period so that the renderer will not be killed immediately after the call.
305 void removeInitialBinding(final int pid) {
306 final ChildProcessConnection connection = sServiceMap.get(pid);
307 if (connection == null) {
308 LogPidWarning(pid, "Tried to remove a binding for a non-existent connection");
311 if (!connection.isInitialBindingBound()) return;
312 ThreadUtils.postOnUiThreadDelayed(new Runnable() {
315 synchronized (mCountLock) {
316 if (connection.isInitialBindingBound()) {
317 decrementOomCount(pid);
318 connection.removeInitialBinding();
322 }, REMOVE_INITIAL_BINDING_DELAY_MILLIS);
326 * Bind a child process as a high priority process so that it has the same priority as the
327 * main process. This can be used for the foreground renderer process to distinguish it from
328 * the background renderer process.
329 * @param pid The process handle of the service connection.
331 void bindAsHighPriority(final int pid) {
332 ChildProcessConnection connection = sServiceMap.get(pid);
333 if (connection == null) {
334 LogPidWarning(pid, "Tried to bind a non-existent connection");
337 synchronized (mCountLock) {
338 connection.attachAsActive();
339 incrementOomCount(pid);
344 * Unbind a high priority process which was previous bound with bindAsHighPriority.
345 * @param pid The process handle of the service.
347 void unbindAsHighPriority(final int pid) {
348 final ChildProcessConnection connection = sServiceMap.get(pid);
349 if (connection == null) {
350 LogPidWarning(pid, "Tried to unbind non-existent connection");
353 if (!connection.isStrongBindingBound()) return;
355 // This runnable performs the actual unbinding. It will be executed synchronously when
356 // on low-end devices and posted with a delay otherwise.
357 Runnable doUnbind = new Runnable() {
360 synchronized (mCountLock) {
361 if (connection.isStrongBindingBound()) {
362 decrementOomCount(pid);
363 connection.detachAsActive();
369 if (SysUtils.isLowEndDevice()) {
372 ThreadUtils.postOnUiThreadDelayed(doUnbind, DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS);
377 * @return True iff the given service process is protected from the out-of-memory killing,
378 * or it was protected when it died (either crashed or was closed). This can be used to
379 * decide if a disconnection of a renderer was a crash or a probable out-of-memory kill. In
380 * the unlikely event of the OS reusing renderer pid, the call will refer to the most recent
381 * renderer of the given pid. The binding count is being reset in addNewConnection().
383 boolean isOomProtected(int pid) {
384 synchronized (mCountLock) {
385 return mOomBindingCount.get(pid) > 0;
390 private static BindingManager sBindingManager = new BindingManager();
392 static BindingManager getBindingManager() {
393 return sBindingManager;
397 * Returns the child process service interface for the given pid. This may be called on
398 * any thread, but the caller must assume that the service can disconnect at any time. All
399 * service calls should catch and handle android.os.RemoteException.
401 * @param pid The pid (process handle) of the service obtained from {@link #start}.
402 * @return The IChildProcessService or null if the service no longer exists.
404 public static IChildProcessService getChildService(int pid) {
405 ChildProcessConnection connection = sServiceMap.get(pid);
406 if (connection != null) {
407 return connection.getService();
413 * Should be called early in startup so the work needed to spawn the child process can be done
414 * in parallel to other startup work. Must not be called on the UI thread. Spare connection is
415 * created in sandboxed child process.
416 * @param context the application context used for the connection.
418 public static void warmUp(Context context) {
419 synchronized (ChildProcessLauncher.class) {
420 assert !ThreadUtils.runningOnUiThread();
421 if (sSpareSandboxedConnection == null) {
422 sSpareSandboxedConnection = allocateBoundConnection(context, null, true);
427 private static String getSwitchValue(final String[] commandLine, String switchKey) {
428 if (commandLine == null || switchKey == null) {
431 // This format should be matched with the one defined in command_line.h.
432 final String switchKeyPrefix = "--" + switchKey + "=";
433 for (String command : commandLine) {
434 if (command != null && command.startsWith(switchKeyPrefix)) {
435 return command.substring(switchKeyPrefix.length());
442 * Spawns and connects to a child process. May be called on any thread. It will not block, but
443 * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is
444 * established. Note this callback will not necessarily be from the same thread (currently it
445 * always comes from the main thread).
447 * @param context Context used to obtain the application context.
448 * @param commandLine The child process command line argv.
449 * @param file_ids The ID that should be used when mapping files in the created process.
450 * @param file_fds The file descriptors that should be mapped in the created process.
451 * @param file_auto_close Whether the file descriptors should be closed once they were passed to
452 * the created process.
453 * @param clientContext Arbitrary parameter used by the client to distinguish this connection.
458 final String[] commandLine,
461 boolean[] fileAutoClose,
462 final int clientContext) {
463 assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length;
464 FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
465 for (int i = 0; i < fileFds.length; i++) {
467 new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
469 assert clientContext != 0;
471 int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
472 boolean inSandbox = true;
473 String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
474 if (SWITCH_RENDERER_PROCESS.equals(processType)) {
475 callbackType = CALLBACK_FOR_RENDERER_PROCESS;
476 } else if (SWITCH_GPU_PROCESS.equals(processType)) {
477 callbackType = CALLBACK_FOR_GPU_PROCESS;
478 } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) {
482 ChildProcessConnection allocatedConnection = null;
483 synchronized (ChildProcessLauncher.class) {
485 allocatedConnection = sSpareSandboxedConnection;
486 sSpareSandboxedConnection = null;
489 if (allocatedConnection == null) {
490 allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox);
491 if (allocatedConnection == null) {
492 // Notify the native code so it can free the heap allocated callback.
493 nativeOnChildProcessStarted(clientContext, 0);
497 final ChildProcessConnection connection = allocatedConnection;
498 Log.d(TAG, "Setting up connection to process: slot=" + connection.getServiceNumber());
500 ChildProcessConnection.ConnectionCallback connectionCallback =
501 new ChildProcessConnection.ConnectionCallback() {
502 public void onConnected(int pid) {
503 Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext);
504 if (pid != NULL_PROCESS_HANDLE) {
505 sBindingManager.addNewConnection(pid);
506 sServiceMap.put(pid, connection);
508 freeConnection(connection);
510 nativeOnChildProcessStarted(clientContext, pid);
514 // TODO(sievers): Revisit this as it doesn't correctly handle the utility process
515 // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS;
517 connection.setupConnection(commandLine,
519 createCallback(callbackType),
521 Linker.getSharedRelros());
525 * Terminates a child process. This may be called from any thread.
527 * @param pid The pid (process handle) of the service connection obtained from {@link #start}.
530 static void stop(int pid) {
531 Log.d(TAG, "stopping child connection: pid=" + pid);
532 ChildProcessConnection connection = sServiceMap.remove(pid);
533 if (connection == null) {
534 LogPidWarning(pid, "Tried to stop non-existent connection");
538 freeConnection(connection);
542 * This implementation is used to receive callbacks from the remote service.
544 private static IChildProcessCallback createCallback(final int callbackType) {
545 return new IChildProcessCallback.Stub() {
547 * This is called by the remote service regularly to tell us about new values. Note that
548 * IPC calls are dispatched through a thread pool running in each process, so the code
549 * executing here will NOT be running in our main thread -- so, to update the UI, we
550 * need to use a Handler.
553 public void establishSurfacePeer(
554 int pid, Surface surface, int primaryID, int secondaryID) {
555 // Do not allow a malicious renderer to connect to a producer. This is only used
556 // from stream textures managed by the GPU process.
557 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
558 Log.e(TAG, "Illegal callback for non-GPU process.");
562 nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID);
566 public Surface getViewSurface(int surfaceId) {
567 // Do not allow a malicious renderer to get to our view surface.
568 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
569 Log.e(TAG, "Illegal callback for non-GPU process.");
573 return nativeGetViewSurface(surfaceId);
578 private static void LogPidWarning(int pid, String message) {
579 // This class is effectively a no-op in single process mode, so don't log warnings there.
580 if (pid > 0 && !nativeIsSingleProcess()) {
581 Log.w(TAG, message + ", pid=" + pid);
585 private static native void nativeOnChildProcessStarted(int clientContext, int pid);
586 private static native Surface nativeGetViewSurface(int surfaceId);
587 private static native void nativeEstablishSurfacePeer(
588 int pid, Surface surface, int primaryID, int secondaryID);
589 private static native boolean nativeIsSingleProcess();