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