ChildProcessConnection.java revision f2477e01787aa58f445919b809d89e252beef54f
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.ComponentName; 8import android.content.Context; 9import android.content.Intent; 10import android.content.ServiceConnection; 11import android.os.Bundle; 12import android.os.IBinder; 13import android.os.ParcelFileDescriptor; 14import android.util.Log; 15 16import org.chromium.base.CpuFeatures; 17import org.chromium.base.ThreadUtils; 18import org.chromium.content.app.ChildProcessService; 19import org.chromium.content.app.Linker; 20import org.chromium.content.app.LinkerParams; 21import org.chromium.content.common.IChildProcessCallback; 22import org.chromium.content.common.IChildProcessService; 23import org.chromium.content.common.TraceEvent; 24 25import java.io.IOException; 26 27/** 28 * Manages a connection between the browser activity and a child service. The class is responsible 29 * for estabilishing the connection (start()), closing it (stop()) and increasing the priority of 30 * the service when it is in active use (between calls to attachAsActive() and detachAsActive()). 31 */ 32public class ChildProcessConnection { 33 /** 34 * Used to notify the consumer about disconnection of the service. This callback is provided 35 * earlier than ConnectionCallbacks below, as a child process might die before the connection is 36 * fully set up. 37 */ 38 interface DeathCallback { 39 void onChildProcessDied(int pid); 40 } 41 42 /** 43 * Used to notify the consumer about the connection being established. 44 */ 45 interface ConnectionCallback { 46 /** 47 * Called when the connection to the service is established. 48 * @param pid Pid of the child process. 49 */ 50 void onConnected(int pid); 51 } 52 53 // Names of items placed in the bind intent or connection bundle. 54 public static final String EXTRA_COMMAND_LINE = 55 "com.google.android.apps.chrome.extra.command_line"; 56 // Note the FDs may only be passed in the connection bundle. 57 public static final String EXTRA_FILES_PREFIX = 58 "com.google.android.apps.chrome.extra.extraFile_"; 59 public static final String EXTRA_FILES_ID_SUFFIX = "_id"; 60 public static final String EXTRA_FILES_FD_SUFFIX = "_fd"; 61 62 // Used to pass the CPU core count to child processes. 63 public static final String EXTRA_CPU_COUNT = 64 "com.google.android.apps.chrome.extra.cpu_count"; 65 // Used to pass the CPU features mask to child processes. 66 public static final String EXTRA_CPU_FEATURES = 67 "com.google.android.apps.chrome.extra.cpu_features"; 68 69 private final Context mContext; 70 private final int mServiceNumber; 71 private final boolean mInSandbox; 72 private final ChildProcessConnection.DeathCallback mDeathCallback; 73 private final Class<? extends ChildProcessService> mServiceClass; 74 75 // Synchronization: While most internal flow occurs on the UI thread, the public API 76 // (specifically start and stop) may be called from any thread, hence all entry point methods 77 // into the class are synchronized on the lock to protect access to these members. But see also 78 // the TODO where AsyncBoundServiceConnection is created. 79 private final Object mLock = new Object(); 80 private IChildProcessService mService = null; 81 // Set to true when the service connect is finished, even if it fails. 82 private boolean mServiceConnectComplete = false; 83 // Set to true when the service disconnects, as opposed to being properly closed. This happens 84 // when the process crashes or gets killed by the system out-of-memory killer. 85 private boolean mServiceDisconnected = false; 86 private int mPID = 0; // Process ID of the corresponding child process. 87 // Initial binding protects the newly spawned process from being killed before it is put to use, 88 // it is maintained between calls to start() and removeInitialBinding(). 89 private ChildServiceConnection mInitialBinding = null; 90 // Strong binding will make the service priority equal to the priority of the activity. We want 91 // the OS to be able to kill background renderers as it kills other background apps, so strong 92 // bindings are maintained only for services that are active at the moment (between 93 // attachAsActive() and detachAsActive()). 94 private ChildServiceConnection mStrongBinding = null; 95 // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls 96 // to start() and stop(). 97 private ChildServiceConnection mWaivedBinding = null; 98 // Incremented on attachAsActive(), decremented on detachAsActive(). 99 private int mAttachAsActiveCount = 0; 100 101 // Linker-related parameters. 102 private LinkerParams mLinkerParams = null; 103 104 private static final String TAG = "ChildProcessConnection"; 105 106 private static class ConnectionParams { 107 final String[] mCommandLine; 108 final FileDescriptorInfo[] mFilesToBeMapped; 109 final IChildProcessCallback mCallback; 110 final Bundle mSharedRelros; 111 112 ConnectionParams(String[] commandLine, FileDescriptorInfo[] filesToBeMapped, 113 IChildProcessCallback callback, Bundle sharedRelros) { 114 mCommandLine = commandLine; 115 mFilesToBeMapped = filesToBeMapped; 116 mCallback = callback; 117 mSharedRelros = sharedRelros; 118 } 119 } 120 121 // This is set by the consumer of the class in setupConnection() and is later used in 122 // doSetupConnection(), after which the variable is cleared. Therefore this is only valid while 123 // the connection is being set up. 124 private ConnectionParams mConnectionParams; 125 126 // Callbacks used to notify the consumer about connection events. This is also provided in 127 // setupConnection(), but remains valid after setup. 128 private ChildProcessConnection.ConnectionCallback mConnectionCallback; 129 130 private class ChildServiceConnection implements ServiceConnection { 131 private boolean mBound = false; 132 133 private final int mBindFlags; 134 135 public ChildServiceConnection(int bindFlags) { 136 mBindFlags = bindFlags; 137 } 138 139 boolean bind(String[] commandLine) { 140 if (!mBound) { 141 final Intent intent = createServiceBindIntent(); 142 if (commandLine != null) { 143 intent.putExtra(EXTRA_COMMAND_LINE, commandLine); 144 } 145 if (mLinkerParams != null) 146 mLinkerParams.addIntentExtras(intent); 147 mBound = mContext.bindService(intent, this, mBindFlags); 148 } 149 return mBound; 150 } 151 152 void unbind() { 153 if (mBound) { 154 mContext.unbindService(this); 155 mBound = false; 156 } 157 } 158 159 boolean isBound() { 160 return mBound; 161 } 162 163 @Override 164 public void onServiceConnected(ComponentName className, IBinder service) { 165 synchronized (mLock) { 166 // A flag from the parent class ensures we run the post-connection logic only once 167 // (instead of once per each ChildServiceConnection). 168 if (mServiceConnectComplete) { 169 return; 170 } 171 TraceEvent.begin(); 172 mServiceConnectComplete = true; 173 mService = IChildProcessService.Stub.asInterface(service); 174 // Make sure that the connection parameters have already been provided. If not, 175 // doConnectionSetup() will be called from setupConnection(). 176 if (mConnectionParams != null) { 177 doConnectionSetup(); 178 } 179 TraceEvent.end(); 180 } 181 } 182 183 184 // Called on the main thread to notify that the child service did not disconnect gracefully. 185 @Override 186 public void onServiceDisconnected(ComponentName className) { 187 // Ensure that the disconnection logic runs only once (instead of once per each 188 // ChildServiceConnection). 189 if (mServiceDisconnected) { 190 return; 191 } 192 mServiceDisconnected = true; 193 int pid = mPID; // Stash the pid for DeathCallback since stop() will clear it. 194 boolean disconnectedWhileBeingSetUp = mConnectionParams != null; 195 Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=" + pid); 196 stop(); // We don't want to auto-restart on crash. Let the browser do that. 197 if (pid != 0) { 198 mDeathCallback.onChildProcessDied(pid); 199 } 200 // TODO(ppi): does anyone know why we need to do that? 201 if (disconnectedWhileBeingSetUp && mConnectionCallback != null) { 202 mConnectionCallback.onConnected(0); 203 } 204 } 205 } 206 207 ChildProcessConnection(Context context, int number, boolean inSandbox, 208 ChildProcessConnection.DeathCallback deathCallback, 209 Class<? extends ChildProcessService> serviceClass, 210 LinkerParams linkerParams) { 211 mContext = context; 212 mServiceNumber = number; 213 mInSandbox = inSandbox; 214 mDeathCallback = deathCallback; 215 mServiceClass = serviceClass; 216 mLinkerParams = linkerParams; 217 mInitialBinding = new ChildServiceConnection(Context.BIND_AUTO_CREATE); 218 mStrongBinding = new ChildServiceConnection( 219 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); 220 mWaivedBinding = new ChildServiceConnection( 221 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY); 222 } 223 224 int getServiceNumber() { 225 return mServiceNumber; 226 } 227 228 boolean isInSandbox() { 229 return mInSandbox; 230 } 231 232 IChildProcessService getService() { 233 synchronized (mLock) { 234 return mService; 235 } 236 } 237 238 private Intent createServiceBindIntent() { 239 Intent intent = new Intent(); 240 intent.setClassName(mContext, mServiceClass.getName() + mServiceNumber); 241 intent.setPackage(mContext.getPackageName()); 242 return intent; 243 } 244 245 /** 246 * Starts a connection to an IChildProcessService. This must be followed by a call to 247 * setupConnection() to setup the connection parameters. start() and setupConnection() are 248 * separate to allow the client to pass whatever parameters they have available here, and 249 * complete the remainder later while reducing the connection setup latency. 250 * @param commandLine (Optional) Command line for the child process. If omitted, then 251 * the command line parameters must instead be passed to setupConnection(). 252 */ 253 void start(String[] commandLine) { 254 synchronized (mLock) { 255 TraceEvent.begin(); 256 assert !ThreadUtils.runningOnUiThread(); 257 258 if (!mInitialBinding.bind(commandLine)) { 259 onBindFailed(); 260 } else { 261 mWaivedBinding.bind(null); 262 } 263 TraceEvent.end(); 264 } 265 } 266 267 /** 268 * Setups the connection after it was started with start(). This method should be called by the 269 * consumer of the class to set up additional connection parameters. 270 * @param commandLine (Optional) will be ignored if the command line was already sent in bind() 271 * @param filesToBeMapped a list of file descriptors that should be registered 272 * @param processCallback Used for status updates regarding this process connection. 273 * @param connectionCallbacks will notify the consumer about the connection being established 274 * and the status of the out-of-memory bindings being bound for the connection. 275 */ 276 void setupConnection( 277 String[] commandLine, 278 FileDescriptorInfo[] filesToBeMapped, 279 IChildProcessCallback processCallback, 280 ConnectionCallback connectionCallbacks, 281 Bundle sharedRelros) { 282 synchronized (mLock) { 283 TraceEvent.begin(); 284 assert mConnectionParams == null; 285 mConnectionCallback = connectionCallbacks; 286 mConnectionParams = new ConnectionParams( 287 commandLine, filesToBeMapped, processCallback, sharedRelros); 288 // Make sure that the service is already connected. If not, doConnectionSetup() will be 289 // called from onServiceConnected(). 290 if (mServiceConnectComplete) { 291 doConnectionSetup(); 292 } 293 TraceEvent.end(); 294 } 295 } 296 297 /** 298 * Terminates the connection to IChildProcessService, closing all bindings. It is safe to call 299 * this multiple times. 300 */ 301 void stop() { 302 synchronized (mLock) { 303 mInitialBinding.unbind(); 304 mStrongBinding.unbind(); 305 mWaivedBinding.unbind(); 306 mAttachAsActiveCount = 0; 307 if (mService != null) { 308 mService = null; 309 mPID = 0; 310 } 311 mConnectionParams = null; 312 mServiceConnectComplete = false; 313 } 314 } 315 316 // Called on the main thread to notify that the bindService() call failed (returned false). 317 private void onBindFailed() { 318 mServiceConnectComplete = true; 319 if (mConnectionParams != null) { 320 doConnectionSetup(); 321 } 322 } 323 324 /** 325 * Called after the connection parameters have been set (in setupConnection()) *and* a 326 * connection has been established (as signaled by onServiceConnected()) or failed (as signaled 327 * by onBindFailed(), in this case mService will be null). These two events can happen in any 328 * order. 329 */ 330 private void doConnectionSetup() { 331 TraceEvent.begin(); 332 assert mServiceConnectComplete && mConnectionParams != null; 333 334 if (mService != null) { 335 Bundle bundle = new Bundle(); 336 bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine); 337 338 FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped; 339 ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileInfos.length]; 340 for (int i = 0; i < fileInfos.length; i++) { 341 if (fileInfos[i].mFd == -1) { 342 // If someone provided an invalid FD, they are doing something wrong. 343 Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for process connection, " 344 + "aborting connection."); 345 return; 346 } 347 String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX; 348 String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX; 349 if (fileInfos[i].mAutoClose) { 350 // Adopt the FD, it will be closed when we close the ParcelFileDescriptor. 351 parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].mFd); 352 } else { 353 try { 354 parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i].mFd); 355 } catch (IOException e) { 356 Log.e(TAG, 357 "Invalid FD provided for process connection, aborting connection.", 358 e); 359 return; 360 } 361 362 } 363 bundle.putParcelable(fdName, parcelFiles[i]); 364 bundle.putInt(idName, fileInfos[i].mId); 365 } 366 // Add the CPU properties now. 367 bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount()); 368 bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask()); 369 370 bundle.putBundle(Linker.EXTRA_LINKER_SHARED_RELROS, 371 mConnectionParams.mSharedRelros); 372 373 try { 374 mPID = mService.setupConnection(bundle, mConnectionParams.mCallback); 375 } catch (android.os.RemoteException re) { 376 Log.e(TAG, "Failed to setup connection.", re); 377 } 378 // We proactively close the FDs rather than wait for GC & finalizer. 379 try { 380 for (ParcelFileDescriptor parcelFile : parcelFiles) { 381 if (parcelFile != null) parcelFile.close(); 382 } 383 } catch (IOException ioe) { 384 Log.w(TAG, "Failed to close FD.", ioe); 385 } 386 } 387 mConnectionParams = null; 388 389 if (mConnectionCallback != null) { 390 mConnectionCallback.onConnected(getPid()); 391 } 392 TraceEvent.end(); 393 } 394 395 /** @return true iff the initial oom binding is currently bound. */ 396 boolean isInitialBindingBound() { 397 synchronized (mLock) { 398 return mInitialBinding.isBound(); 399 } 400 } 401 402 /** @return true iff the strong oom binding is currently bound. */ 403 boolean isStrongBindingBound() { 404 synchronized (mLock) { 405 return mStrongBinding.isBound(); 406 } 407 } 408 409 /** 410 * Called to remove the strong binding estabilished when the connection was started. It is safe 411 * to call this multiple times. 412 */ 413 void removeInitialBinding() { 414 synchronized (mLock) { 415 mInitialBinding.unbind(); 416 } 417 } 418 419 /** 420 * Unbinds the bindings that protect the process from oom killing. It is safe to call this 421 * multiple times, before as well as after stop(). 422 */ 423 void dropOomBindings() { 424 synchronized (mLock) { 425 mInitialBinding.unbind(); 426 427 mAttachAsActiveCount = 0; 428 mStrongBinding.unbind(); 429 } 430 } 431 432 /** 433 * Called when the service becomes active, ie important to the caller. This is handled by 434 * setting up a binding that will make the service as important as the main process. We allow 435 * callers to indicate the same connection as active multiple times. Instead of maintaining 436 * multiple bindings, we count the requests and unbind when the count drops to zero. 437 */ 438 void attachAsActive() { 439 synchronized (mLock) { 440 if (mService == null) { 441 Log.w(TAG, "The connection is not bound for " + mPID); 442 return; 443 } 444 if (mAttachAsActiveCount == 0) { 445 mStrongBinding.bind(null); 446 } 447 mAttachAsActiveCount++; 448 } 449 } 450 451 /** 452 * Called when the service is no longer considered active. 453 */ 454 void detachAsActive() { 455 synchronized (mLock) { 456 if (mService == null) { 457 Log.w(TAG, "The connection is not bound for " + mPID); 458 return; 459 } 460 assert mAttachAsActiveCount > 0; 461 mAttachAsActiveCount--; 462 if (mAttachAsActiveCount == 0) { 463 mStrongBinding.unbind(); 464 } 465 } 466 } 467 468 /** 469 * @return The connection PID, or 0 if not yet connected. 470 */ 471 int getPid() { 472 synchronized (mLock) { 473 return mPID; 474 } 475 } 476} 477