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