ChildProcessLauncher.java revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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. 4 5package org.chromium.content.browser; 6 7import android.content.Context; 8import android.util.Log; 9import android.util.SparseIntArray; 10import android.view.Surface; 11 12import java.util.ArrayList; 13import java.util.Map; 14import java.util.concurrent.ConcurrentHashMap; 15 16import org.chromium.base.CalledByNative; 17import org.chromium.base.JNINamespace; 18import org.chromium.base.SysUtils; 19import org.chromium.base.ThreadUtils; 20import org.chromium.content.app.ChildProcessService; 21import org.chromium.content.app.PrivilegedProcessService; 22import org.chromium.content.app.SandboxedProcessService; 23import org.chromium.content.common.IChildProcessCallback; 24import org.chromium.content.common.IChildProcessService; 25 26/** 27 * This class provides the method to start/stop ChildProcess called by native. 28 */ 29@JNINamespace("content") 30public class ChildProcessLauncher { 31 private static String TAG = "ChildProcessLauncher"; 32 33 private static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0; 34 private static final int CALLBACK_FOR_GPU_PROCESS = 1; 35 private static final int CALLBACK_FOR_RENDERER_PROCESS = 2; 36 37 private static final String SWITCH_PROCESS_TYPE = "type"; 38 private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker"; 39 private static final String SWITCH_RENDERER_PROCESS = "renderer"; 40 private static final String SWITCH_GPU_PROCESS = "gpu-process"; 41 42 // The upper limit on the number of simultaneous sandboxed and privileged child service process 43 // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX 44 // classes and PrivilegedProcessServiceX classes declared in this package and defined as 45 // services in the embedding application's manifest file. 46 // (See {@link ChildProcessService} for more details on defining the services.) 47 /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 13; 48 /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3; 49 50 private static class ChildConnectionAllocator { 51 // Connections to services. Indices of the array correspond to the service numbers. 52 private ChildProcessConnection[] mChildProcessConnections; 53 54 // The list of free (not bound) service indices. When looking for a free service, the first 55 // index in that list should be used. When a service is unbound, its index is added to the 56 // end of the list. This is so that we avoid immediately reusing the freed service (see 57 // http://crbug.com/164069): the framework might keep a service process alive when it's been 58 // unbound for a short time. If a new connection to the same service is bound at that point, 59 // the process is reused and bad things happen (mostly static variables are set when we 60 // don't expect them to). 61 // SHOULD BE ACCESSED WITH mConnectionLock. 62 private ArrayList<Integer> mFreeConnectionIndices; 63 private final Object mConnectionLock = new Object(); 64 65 private Class<? extends ChildProcessService> mChildClass; 66 private final boolean mInSandbox; 67 68 public ChildConnectionAllocator(boolean inSandbox) { 69 int numChildServices = inSandbox ? 70 MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES; 71 mChildProcessConnections = new ChildProcessConnection[numChildServices]; 72 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); 73 for (int i = 0; i < numChildServices; i++) { 74 mFreeConnectionIndices.add(i); 75 } 76 setServiceClass(inSandbox ? 77 SandboxedProcessService.class : PrivilegedProcessService.class); 78 mInSandbox = inSandbox; 79 } 80 81 public void setServiceClass(Class<? extends ChildProcessService> childClass) { 82 mChildClass = childClass; 83 } 84 85 public ChildProcessConnection allocate( 86 Context context, ChildProcessConnection.DeathCallback deathCallback) { 87 synchronized(mConnectionLock) { 88 if (mFreeConnectionIndices.isEmpty()) { 89 Log.w(TAG, "Ran out of service." ); 90 return null; 91 } 92 int slot = mFreeConnectionIndices.remove(0); 93 assert mChildProcessConnections[slot] == null; 94 mChildProcessConnections[slot] = new ChildProcessConnection(context, slot, 95 mInSandbox, deathCallback, mChildClass); 96 return mChildProcessConnections[slot]; 97 } 98 } 99 100 public void free(ChildProcessConnection connection) { 101 synchronized(mConnectionLock) { 102 int slot = connection.getServiceNumber(); 103 if (mChildProcessConnections[slot] != connection) { 104 int occupier = mChildProcessConnections[slot] == null ? 105 -1 : mChildProcessConnections[slot].getServiceNumber(); 106 Log.e(TAG, "Unable to find connection to free in slot: " + slot + 107 " already occupied by service: " + occupier); 108 assert false; 109 } else { 110 mChildProcessConnections[slot] = null; 111 assert !mFreeConnectionIndices.contains(slot); 112 mFreeConnectionIndices.add(slot); 113 } 114 } 115 } 116 } 117 118 // Service class for child process. As the default value it uses SandboxedProcessService0 and 119 // PrivilegedProcessService0. 120 private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator = 121 new ChildConnectionAllocator(true); 122 private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator = 123 new ChildConnectionAllocator(false); 124 125 private static boolean sConnectionAllocated = false; 126 127 // Sets service class for sandboxed service and privileged service. 128 public static void setChildProcessClass( 129 Class<? extends SandboxedProcessService> sandboxedServiceClass, 130 Class<? extends PrivilegedProcessService> privilegedServiceClass) { 131 // We should guarantee this is called before allocating connection. 132 assert !sConnectionAllocated; 133 sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass); 134 sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass); 135 } 136 137 private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) { 138 return inSandbox ? 139 sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator; 140 } 141 142 private static ChildProcessConnection allocateConnection(Context context, boolean inSandbox) { 143 ChildProcessConnection.DeathCallback deathCallback = 144 new ChildProcessConnection.DeathCallback() { 145 @Override 146 public void onChildProcessDied(int pid) { 147 stop(pid); 148 } 149 }; 150 sConnectionAllocated = true; 151 return getConnectionAllocator(inSandbox).allocate(context, deathCallback); 152 } 153 154 private static ChildProcessConnection allocateBoundConnection(Context context, 155 String[] commandLine, boolean inSandbox) { 156 ChildProcessConnection connection = allocateConnection(context, inSandbox); 157 if (connection != null) { 158 connection.start(commandLine); 159 } 160 return connection; 161 } 162 163 private static void freeConnection(ChildProcessConnection connection) { 164 if (connection == null) { 165 return; 166 } 167 getConnectionAllocator(connection.isInSandbox()).free(connection); 168 return; 169 } 170 171 // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle. 172 private static final int NULL_PROCESS_HANDLE = 0; 173 174 // Map from pid to ChildService connection. 175 private static Map<Integer, ChildProcessConnection> sServiceMap = 176 new ConcurrentHashMap<Integer, ChildProcessConnection>(); 177 178 // A pre-allocated and pre-bound connection ready for connection setup, or null. 179 private static ChildProcessConnection sSpareSandboxedConnection = null; 180 181 /** 182 * Manages oom bindings used to bound child services. "Oom binding" is a binding that raises the 183 * process oom priority so that it shouldn't be killed by the OS out-of-memory killer under 184 * normal conditions (it can still be killed under drastic memory pressure). 185 * 186 * This class serves a proxy between external calls that manipulate the bindings and the 187 * connections, allowing to enforce policies such as delayed removal of the bindings. 188 */ 189 static class BindingManager { 190 // Delay of 1 second used when removing the initial oom binding of a process. 191 private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000; 192 193 // Delay of 5 second used when removing temporary strong binding of a process (only on 194 // non-low-memory devices). 195 private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 5 * 1000; 196 197 // Map from pid to the count of oom bindings bound for the service. Should be accessed with 198 // mCountLock. 199 private final SparseIntArray mOomBindingCount = new SparseIntArray(); 200 201 // Should be acquired before binding or unbinding the connections and modifying 202 // mOomBindingCount. 203 private final Object mCountLock = new Object(); 204 205 /** 206 * Registers new oom binging bound for a child process. Should be called with mCountLock. 207 * @param pid handle of the process. 208 */ 209 private void incrementOomCount(int pid) { 210 mOomBindingCount.put(pid, mOomBindingCount.get(pid) + 1); 211 } 212 213 /** 214 * Registers an oom binging unbound for a child process. Should be called with mCountLock. 215 * @param pid handle of the process. 216 */ 217 private void decrementOomCount(int pid) { 218 int count = mOomBindingCount.get(pid, -1); 219 assert count > 0; 220 count--; 221 if (count > 0) { 222 mOomBindingCount.put(pid, count); 223 } else { 224 mOomBindingCount.delete(pid); 225 } 226 } 227 228 /** 229 * Registers a freshly started child process. 230 * @param pid handle of the process. 231 */ 232 void addNewConnection(int pid) { 233 // Every new connection is bound with initial oom binding. 234 synchronized (mCountLock) { 235 mOomBindingCount.put(pid, 1); 236 } 237 } 238 239 /** 240 * Remove the initial binding of the child process. Child processes are bound with initial 241 * binding to protect them from getting killed before they are put to use. This method 242 * allows to remove the binding once it is no longer needed. The binding is removed after a 243 * fixed delay period so that the renderer will not be killed immediately after the call. 244 */ 245 void removeInitialBinding(final int pid) { 246 final ChildProcessConnection connection = sServiceMap.get(pid); 247 if (connection == null) { 248 LogPidWarning(pid, "Tried to remove a binding for a non-existent connection"); 249 return; 250 } 251 if (!connection.isInitialBindingBound()) return; 252 ThreadUtils.postOnUiThreadDelayed(new Runnable() { 253 @Override 254 public void run() { 255 synchronized (mCountLock) { 256 if (connection.isInitialBindingBound()) { 257 decrementOomCount(pid); 258 connection.removeInitialBinding(); 259 } 260 } 261 } 262 }, REMOVE_INITIAL_BINDING_DELAY_MILLIS); 263 } 264 265 /** 266 * Bind a child process as a high priority process so that it has the same priority as the 267 * main process. This can be used for the foreground renderer process to distinguish it from 268 * the the background renderer process. 269 * @param pid The process handle of the service connection. 270 */ 271 void bindAsHighPriority(final int pid) { 272 ChildProcessConnection connection = sServiceMap.get(pid); 273 if (connection == null) { 274 LogPidWarning(pid, "Tried to bind a non-existent connection"); 275 return; 276 } 277 synchronized (mCountLock) { 278 connection.attachAsActive(); 279 incrementOomCount(pid); 280 } 281 } 282 283 /** 284 * Unbind a high priority process which was previous bound with bindAsHighPriority. 285 * @param pid The process handle of the service. 286 */ 287 void unbindAsHighPriority(final int pid) { 288 final ChildProcessConnection connection = sServiceMap.get(pid); 289 if (connection == null) { 290 LogPidWarning(pid, "Tried to unbind non-existent connection"); 291 return; 292 } 293 ThreadUtils.postOnUiThreadDelayed(new Runnable() { 294 @Override 295 public void run() { 296 synchronized (mCountLock) { 297 decrementOomCount(pid); 298 connection.detachAsActive(); 299 } 300 } 301 }, SysUtils.isLowEndDevice() ? 0 : DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS); 302 } 303 304 /** 305 * @return True iff the given service process is protected from the out-of-memory killing, 306 * or it was protected when it died (either crashed or was closed). This can be used to 307 * decide if a disconnection of a renderer was a crash or a probable out-of-memory kill. In 308 * the unlikely event of the OS reusing renderer pid, the call will refer to the most recent 309 * renderer of the given pid. The binding count is being reset in addNewConnection(). 310 */ 311 boolean isOomProtected(int pid) { 312 synchronized (mCountLock) { 313 return mOomBindingCount.get(pid) > 0; 314 } 315 } 316 } 317 318 private static BindingManager sBindingManager = new BindingManager(); 319 320 static BindingManager getBindingManager() { 321 return sBindingManager; 322 } 323 324 /** 325 * Returns the child process service interface for the given pid. This may be called on 326 * any thread, but the caller must assume that the service can disconnect at any time. All 327 * service calls should catch and handle android.os.RemoteException. 328 * 329 * @param pid The pid (process handle) of the service obtained from {@link #start}. 330 * @return The IChildProcessService or null if the service no longer exists. 331 */ 332 public static IChildProcessService getChildService(int pid) { 333 ChildProcessConnection connection = sServiceMap.get(pid); 334 if (connection != null) { 335 return connection.getService(); 336 } 337 return null; 338 } 339 340 /** 341 * Should be called early in startup so the work needed to spawn the child process can be done 342 * in parallel to other startup work. Must not be called on the UI thread. Spare connection is 343 * created in sandboxed child process. 344 * @param context the application context used for the connection. 345 */ 346 public static void warmUp(Context context) { 347 synchronized (ChildProcessLauncher.class) { 348 assert !ThreadUtils.runningOnUiThread(); 349 if (sSpareSandboxedConnection == null) { 350 sSpareSandboxedConnection = allocateBoundConnection(context, null, true); 351 } 352 } 353 } 354 355 private static String getSwitchValue(final String[] commandLine, String switchKey) { 356 if (commandLine == null || switchKey == null) { 357 return null; 358 } 359 // This format should be matched with the one defined in command_line.h. 360 final String switchKeyPrefix = "--" + switchKey + "="; 361 for (String command : commandLine) { 362 if (command != null && command.startsWith(switchKeyPrefix)) { 363 return command.substring(switchKeyPrefix.length()); 364 } 365 } 366 return null; 367 } 368 369 /** 370 * Spawns and connects to a child process. May be called on any thread. It will not block, but 371 * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is 372 * established. Note this callback will not necessarily be from the same thread (currently it 373 * always comes from the main thread). 374 * 375 * @param context Context used to obtain the application context. 376 * @param commandLine The child process command line argv. 377 * @param file_ids The ID that should be used when mapping files in the created process. 378 * @param file_fds The file descriptors that should be mapped in the created process. 379 * @param file_auto_close Whether the file descriptors should be closed once they were passed to 380 * the created process. 381 * @param clientContext Arbitrary parameter used by the client to distinguish this connection. 382 */ 383 @CalledByNative 384 static void start( 385 Context context, 386 final String[] commandLine, 387 int[] fileIds, 388 int[] fileFds, 389 boolean[] fileAutoClose, 390 final int clientContext) { 391 assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length; 392 FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length]; 393 for (int i = 0; i < fileFds.length; i++) { 394 filesToBeMapped[i] = 395 new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]); 396 } 397 assert clientContext != 0; 398 399 int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS; 400 boolean inSandbox = true; 401 String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE); 402 if (SWITCH_RENDERER_PROCESS.equals(processType)) { 403 callbackType = CALLBACK_FOR_RENDERER_PROCESS; 404 } else if (SWITCH_GPU_PROCESS.equals(processType)) { 405 callbackType = CALLBACK_FOR_GPU_PROCESS; 406 } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) { 407 inSandbox = false; 408 } 409 410 ChildProcessConnection allocatedConnection = null; 411 synchronized (ChildProcessLauncher.class) { 412 if (inSandbox) { 413 allocatedConnection = sSpareSandboxedConnection; 414 sSpareSandboxedConnection = null; 415 } 416 } 417 if (allocatedConnection == null) { 418 allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox); 419 if (allocatedConnection == null) { 420 // Notify the native code so it can free the heap allocated callback. 421 nativeOnChildProcessStarted(clientContext, 0); 422 return; 423 } 424 } 425 final ChildProcessConnection connection = allocatedConnection; 426 Log.d(TAG, "Setting up connection to process: slot=" + connection.getServiceNumber()); 427 428 ChildProcessConnection.ConnectionCallback connectionCallback = 429 new ChildProcessConnection.ConnectionCallback() { 430 public void onConnected(int pid) { 431 Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext); 432 if (pid != NULL_PROCESS_HANDLE) { 433 sBindingManager.addNewConnection(pid); 434 sServiceMap.put(pid, connection); 435 } else { 436 freeConnection(connection); 437 } 438 nativeOnChildProcessStarted(clientContext, pid); 439 } 440 441 }; 442 443 // TODO(sievers): Revisit this as it doesn't correctly handle the utility process 444 // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS; 445 446 connection.setupConnection(commandLine, filesToBeMapped, createCallback(callbackType), 447 connectionCallback); 448 } 449 450 /** 451 * Terminates a child process. This may be called from any thread. 452 * 453 * @param pid The pid (process handle) of the service connection obtained from {@link #start}. 454 */ 455 @CalledByNative 456 static void stop(int pid) { 457 Log.d(TAG, "stopping child connection: pid=" + pid); 458 ChildProcessConnection connection = sServiceMap.remove(pid); 459 if (connection == null) { 460 LogPidWarning(pid, "Tried to stop non-existent connection"); 461 return; 462 } 463 connection.stop(); 464 freeConnection(connection); 465 } 466 467 /** 468 * This implementation is used to receive callbacks from the remote service. 469 */ 470 private static IChildProcessCallback createCallback(final int callbackType) { 471 return new IChildProcessCallback.Stub() { 472 /** 473 * This is called by the remote service regularly to tell us about new values. Note that 474 * IPC calls are dispatched through a thread pool running in each process, so the code 475 * executing here will NOT be running in our main thread -- so, to update the UI, we 476 * need to use a Handler. 477 */ 478 @Override 479 public void establishSurfacePeer( 480 int pid, Surface surface, int primaryID, int secondaryID) { 481 // Do not allow a malicious renderer to connect to a producer. This is only used 482 // from stream textures managed by the GPU process. 483 if (callbackType != CALLBACK_FOR_GPU_PROCESS) { 484 Log.e(TAG, "Illegal callback for non-GPU process."); 485 return; 486 } 487 488 nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID); 489 } 490 491 @Override 492 public Surface getViewSurface(int surfaceId) { 493 // Do not allow a malicious renderer to get to our view surface. 494 if (callbackType != CALLBACK_FOR_GPU_PROCESS) { 495 Log.e(TAG, "Illegal callback for non-GPU process."); 496 return null; 497 } 498 499 return nativeGetViewSurface(surfaceId); 500 } 501 }; 502 }; 503 504 private static void LogPidWarning(int pid, String message) { 505 // This class is effectively a no-op in single process mode, so don't log warnings there. 506 if (pid > 0 && !nativeIsSingleProcess()) { 507 Log.w(TAG, message + ", pid=" + pid); 508 } 509 } 510 511 private static native void nativeOnChildProcessStarted(int clientContext, int pid); 512 private static native Surface nativeGetViewSurface(int surfaceId); 513 private static native void nativeEstablishSurfacePeer( 514 int pid, Surface surface, int primaryID, int secondaryID); 515 private static native boolean nativeIsSingleProcess(); 516} 517