ChildProcessConnection.java revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 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.ThreadUtils; 25import org.chromium.content.app.ChildProcessService; 26import org.chromium.content.common.CommandLine; 27import org.chromium.content.common.IChildProcessCallback; 28import org.chromium.content.common.IChildProcessService; 29import org.chromium.content.common.TraceEvent; 30 31public class ChildProcessConnection implements ServiceConnection { 32 interface DeathCallback { 33 void onChildProcessDied(int pid); 34 } 35 36 // Names of items placed in the bind intent or connection bundle. 37 public static final String EXTRA_COMMAND_LINE = 38 "com.google.android.apps.chrome.extra.command_line"; 39 // Note the FDs may only be passed in the connection bundle. 40 public static final String EXTRA_FILES_PREFIX = 41 "com.google.android.apps.chrome.extra.extraFile_"; 42 public static final String EXTRA_FILES_ID_SUFFIX = "_id"; 43 public static final String EXTRA_FILES_FD_SUFFIX = "_fd"; 44 45 // Used to pass the CPU core count to child processes. 46 public static final String EXTRA_CPU_COUNT = 47 "com.google.android.apps.chrome.extra.cpu_count"; 48 // Used to pass the CPU features mask to child processes. 49 public static final String EXTRA_CPU_FEATURES = 50 "com.google.android.apps.chrome.extra.cpu_features"; 51 52 private final Context mContext; 53 private final int mServiceNumber; 54 private final boolean mInSandbox; 55 private final ChildProcessConnection.DeathCallback mDeathCallback; 56 private final Class<? extends ChildProcessService> mServiceClass; 57 58 // Synchronization: While most internal flow occurs on the UI thread, the public API 59 // (specifically bind and unbind) may be called from any thread, hence all entry point methods 60 // into the class are synchronized on the ChildProcessConnection instance to protect access 61 // to these members. But see also the TODO where AsyncBoundServiceConnection is created. 62 private final Object mUiThreadLock = new Object(); 63 private IChildProcessService mService = null; 64 private boolean mServiceConnectComplete = false; 65 private int mPID = 0; // Process ID of the corresponding child process. 66 private HighPriorityConnection mHighPriorityConnection = null; 67 private int mHighPriorityConnectionCount = 0; 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 Runnable mOnConnectionCallback; 76 77 ConnectionParams( 78 String[] commandLine, 79 FileDescriptorInfo[] filesToBeMapped, 80 IChildProcessCallback callback, 81 Runnable onConnectionCallback) { 82 mCommandLine = commandLine; 83 mFilesToBeMapped = filesToBeMapped; 84 mCallback = callback; 85 mOnConnectionCallback = onConnectionCallback; 86 } 87 } 88 89 // This is only valid while the connection is being established. 90 private ConnectionParams mConnectionParams; 91 private boolean mIsBound; 92 93 ChildProcessConnection(Context context, int number, boolean inSandbox, 94 ChildProcessConnection.DeathCallback deathCallback, 95 Class<? extends ChildProcessService> serviceClass) { 96 mContext = context; 97 mServiceNumber = number; 98 mInSandbox = inSandbox; 99 mDeathCallback = deathCallback; 100 mServiceClass = serviceClass; 101 } 102 103 int getServiceNumber() { 104 return mServiceNumber; 105 } 106 107 boolean isInSandbox() { 108 return mInSandbox; 109 } 110 111 IChildProcessService getService() { 112 synchronized(mUiThreadLock) { 113 return mService; 114 } 115 } 116 117 private Intent createServiceBindIntent() { 118 Intent intent = new Intent(); 119 intent.setClassName(mContext, mServiceClass.getName() + mServiceNumber); 120 intent.setPackage(mContext.getPackageName()); 121 return intent; 122 } 123 124 /** 125 * Bind to an IChildProcessService. This must be followed by a call to setupConnection() 126 * to setup the connection parameters. (These methods are separated to allow the client 127 * to pass whatever parameters they have available here, and complete the remainder 128 * later while reducing the connection setup latency). 129 * @param commandLine (Optional) Command line for the child process. If omitted, then 130 * the command line parameters must instead be passed to setupConnection(). 131 */ 132 void bind(String[] commandLine) { 133 synchronized(mUiThreadLock) { 134 TraceEvent.begin(); 135 assert !ThreadUtils.runningOnUiThread(); 136 137 final Intent intent = createServiceBindIntent(); 138 139 if (commandLine != null) { 140 intent.putExtra(EXTRA_COMMAND_LINE, commandLine); 141 } 142 143 mIsBound = mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); 144 if (!mIsBound) { 145 onBindFailed(); 146 } 147 TraceEvent.end(); 148 } 149 } 150 151 /** Setup a connection previous bound via a call to bind(). 152 * 153 * This establishes the parameters that were not already supplied in bind. 154 * @param commandLine (Optional) will be ignored if the command line was already sent in bind() 155 * @param fileToBeMapped a list of file descriptors that should be registered 156 * @param callback Used for status updates regarding this process connection. 157 * @param onConnectionCallback will be run when the connection is setup and ready to use. 158 */ 159 void setupConnection( 160 String[] commandLine, 161 FileDescriptorInfo[] filesToBeMapped, 162 IChildProcessCallback callback, 163 Runnable onConnectionCallback) { 164 synchronized(mUiThreadLock) { 165 TraceEvent.begin(); 166 assert mConnectionParams == null; 167 mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, callback, 168 onConnectionCallback); 169 if (mServiceConnectComplete) { 170 doConnectionSetup(); 171 } 172 TraceEvent.end(); 173 } 174 } 175 176 /** 177 * Unbind the IChildProcessService. It is safe to call this multiple times. 178 */ 179 void unbind() { 180 synchronized(mUiThreadLock) { 181 if (mIsBound) { 182 mContext.unbindService(this); 183 mIsBound = false; 184 } 185 if (mService != null) { 186 if (mHighPriorityConnection != null) { 187 unbindHighPriority(true); 188 } 189 mService = null; 190 mPID = 0; 191 } 192 mConnectionParams = null; 193 mServiceConnectComplete = false; 194 } 195 } 196 197 // Called on the main thread to notify that the service is connected. 198 @Override 199 public void onServiceConnected(ComponentName className, IBinder service) { 200 synchronized(mUiThreadLock) { 201 TraceEvent.begin(); 202 mServiceConnectComplete = true; 203 mService = IChildProcessService.Stub.asInterface(service); 204 if (mConnectionParams != null) { 205 doConnectionSetup(); 206 } 207 TraceEvent.end(); 208 } 209 } 210 211 // Called on the main thread to notify that the bindService() call failed (returned false). 212 private void onBindFailed() { 213 mServiceConnectComplete = true; 214 if (mConnectionParams != null) { 215 doConnectionSetup(); 216 } 217 } 218 219 /** 220 * Called when the connection parameters have been set, and a connection has been established 221 * (as signaled by onServiceConnected), or if the connection failed (mService will be false). 222 */ 223 private void doConnectionSetup() { 224 TraceEvent.begin(); 225 assert mServiceConnectComplete && mConnectionParams != null; 226 // Capture the callback before it is potentially nulled in unbind(). 227 Runnable onConnectionCallback = mConnectionParams.mOnConnectionCallback; 228 if (onConnectionCallback == null) { 229 unbind(); 230 } else if (mService != null) { 231 Bundle bundle = new Bundle(); 232 bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine); 233 234 FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped; 235 ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileInfos.length]; 236 for (int i = 0; i < fileInfos.length; i++) { 237 if (fileInfos[i].mFd == -1) { 238 // If someone provided an invalid FD, they are doing something wrong. 239 Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for process connection, " 240 + "aborting connection."); 241 return; 242 } 243 String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX; 244 String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX; 245 if (fileInfos[i].mAutoClose) { 246 // Adopt the FD, it will be closed when we close the ParcelFileDescriptor. 247 parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].mFd); 248 } else { 249 try { 250 parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i].mFd); 251 } catch(IOException e) { 252 Log.e(TAG, 253 "Invalid FD provided for process connection, aborting connection.", 254 e); 255 return; 256 } 257 258 } 259 bundle.putParcelable(fdName, parcelFiles[i]); 260 bundle.putInt(idName, fileInfos[i].mId); 261 } 262 // Add the CPU properties now. 263 bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount()); 264 bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask()); 265 266 try { 267 mPID = mService.setupConnection(bundle, mConnectionParams.mCallback); 268 } catch (android.os.RemoteException re) { 269 Log.e(TAG, "Failed to setup connection.", re); 270 } 271 // We proactivley close the FDs rather than wait for GC & finalizer. 272 try { 273 for (ParcelFileDescriptor parcelFile : parcelFiles) { 274 if (parcelFile != null) parcelFile.close(); 275 } 276 } catch (IOException ioe) { 277 Log.w(TAG, "Failed to close FD.", ioe); 278 } 279 } 280 mConnectionParams = null; 281 if (onConnectionCallback != null) { 282 onConnectionCallback.run(); 283 } 284 TraceEvent.end(); 285 } 286 287 // Called on the main thread to notify that the child service did not disconnect gracefully. 288 @Override 289 public void onServiceDisconnected(ComponentName className) { 290 int pid = mPID; // Stash pid & connection callback since unbind() will clear them. 291 Runnable onConnectionCallback = 292 mConnectionParams != null ? mConnectionParams.mOnConnectionCallback : null; 293 Log.w(TAG, "onServiceDisconnected (crash?): pid=" + pid); 294 unbind(); // We don't want to auto-restart on crash. Let the browser do that. 295 if (pid != 0) { 296 mDeathCallback.onChildProcessDied(pid); 297 } 298 if (onConnectionCallback != null) { 299 onConnectionCallback.run(); 300 } 301 } 302 303 /** 304 * Bind the service with a new high priority connection. This will make the service 305 * as important as the main process. 306 */ 307 void bindHighPriority() { 308 synchronized(mUiThreadLock) { 309 if (mService == null) { 310 Log.w(TAG, "The connection is not bound for " + mPID); 311 return; 312 } 313 if (mHighPriorityConnection == null) { 314 mHighPriorityConnection = new HighPriorityConnection(); 315 mHighPriorityConnection.bind(); 316 } 317 mHighPriorityConnectionCount++; 318 } 319 } 320 321 /** 322 * Unbind the service as the high priority connection. 323 */ 324 void unbindHighPriority(boolean force) { 325 synchronized(mUiThreadLock) { 326 if (mService == null) { 327 Log.w(TAG, "The connection is not bound for " + mPID); 328 return; 329 } 330 mHighPriorityConnectionCount--; 331 if (force || (mHighPriorityConnectionCount == 0 && mHighPriorityConnection != null)) { 332 mHighPriorityConnection.unbind(); 333 mHighPriorityConnection = null; 334 } 335 } 336 } 337 338 private class HighPriorityConnection implements ServiceConnection { 339 340 private boolean mHBound = false; 341 342 void bind() { 343 final Intent intent = createServiceBindIntent(); 344 345 mHBound = mContext.bindService(intent, this, 346 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); 347 } 348 349 void unbind() { 350 if (mHBound) { 351 mContext.unbindService(this); 352 mHBound = false; 353 } 354 } 355 356 @Override 357 public void onServiceConnected(ComponentName className, IBinder service) { 358 } 359 360 @Override 361 public void onServiceDisconnected(ComponentName className) { 362 } 363 } 364 365 /** 366 * @return The connection PID, or 0 if not yet connected. 367 */ 368 public int getPid() { 369 synchronized(mUiThreadLock) { 370 return mPID; 371 } 372 } 373} 374