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