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