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