- add sources.
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ChildProcessLauncher.java
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
5 package org.chromium.content.browser;
6
7 import android.content.Context;
8 import android.util.Log;
9 import android.util.SparseIntArray;
10 import android.view.Surface;
11
12 import java.util.ArrayList;
13 import java.util.Map;
14 import java.util.concurrent.ConcurrentHashMap;
15
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.base.SysUtils;
19 import org.chromium.base.ThreadUtils;
20 import org.chromium.content.app.ChildProcessService;
21 import org.chromium.content.app.Linker;
22 import org.chromium.content.app.LinkerParams;
23 import org.chromium.content.app.PrivilegedProcessService;
24 import org.chromium.content.app.SandboxedProcessService;
25 import org.chromium.content.common.IChildProcessCallback;
26 import org.chromium.content.common.IChildProcessService;
27
28 /**
29  * This class provides the method to start/stop ChildProcess called by native.
30  */
31 @JNINamespace("content")
32 public class ChildProcessLauncher {
33     private static String TAG = "ChildProcessLauncher";
34
35     private static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0;
36     private static final int CALLBACK_FOR_GPU_PROCESS = 1;
37     private static final int CALLBACK_FOR_RENDERER_PROCESS = 2;
38
39     private static final String SWITCH_PROCESS_TYPE = "type";
40     private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker";
41     private static final String SWITCH_RENDERER_PROCESS = "renderer";
42     private static final String SWITCH_GPU_PROCESS = "gpu-process";
43
44     // The upper limit on the number of simultaneous sandboxed and privileged child service process
45     // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX
46     // classes and PrivilegedProcessServiceX classes declared in this package and defined as
47     // services in the embedding application's manifest file.
48     // (See {@link ChildProcessService} for more details on defining the services.)
49     /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 13;
50     /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3;
51
52     private static class ChildConnectionAllocator {
53         // Connections to services. Indices of the array correspond to the service numbers.
54         private ChildProcessConnection[] mChildProcessConnections;
55
56         // The list of free (not bound) service indices. When looking for a free service, the first
57         // index in that list should be used. When a service is unbound, its index is added to the
58         // end of the list. This is so that we avoid immediately reusing the freed service (see
59         // http://crbug.com/164069): the framework might keep a service process alive when it's been
60         // unbound for a short time. If a new connection to the same service is bound at that point,
61         // the process is reused and bad things happen (mostly static variables are set when we
62         // don't expect them to).
63         // SHOULD BE ACCESSED WITH mConnectionLock.
64         private ArrayList<Integer> mFreeConnectionIndices;
65         private final Object mConnectionLock = new Object();
66
67         private Class<? extends ChildProcessService> mChildClass;
68         private final boolean mInSandbox;
69
70         public ChildConnectionAllocator(boolean inSandbox) {
71             int numChildServices = inSandbox ?
72                     MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES;
73             mChildProcessConnections = new ChildProcessConnection[numChildServices];
74             mFreeConnectionIndices = new ArrayList<Integer>(numChildServices);
75             for (int i = 0; i < numChildServices; i++) {
76                 mFreeConnectionIndices.add(i);
77             }
78             setServiceClass(inSandbox ?
79                     SandboxedProcessService.class : PrivilegedProcessService.class);
80             mInSandbox = inSandbox;
81         }
82
83         public void setServiceClass(Class<? extends ChildProcessService> childClass) {
84             mChildClass = childClass;
85         }
86
87         public ChildProcessConnection allocate(
88                 Context context, ChildProcessConnection.DeathCallback deathCallback,
89                 LinkerParams linkerParams) {
90             synchronized(mConnectionLock) {
91                 if (mFreeConnectionIndices.isEmpty()) {
92                     Log.w(TAG, "Ran out of service." );
93                     return null;
94                 }
95                 int slot = mFreeConnectionIndices.remove(0);
96                 assert mChildProcessConnections[slot] == null;
97                 mChildProcessConnections[slot] = new ChildProcessConnection(context, slot,
98                         mInSandbox, deathCallback, mChildClass, linkerParams);
99                 return mChildProcessConnections[slot];
100             }
101         }
102
103         public void free(ChildProcessConnection connection) {
104             synchronized(mConnectionLock) {
105                 int slot = connection.getServiceNumber();
106                 if (mChildProcessConnections[slot] != connection) {
107                     int occupier = mChildProcessConnections[slot] == null ?
108                             -1 : mChildProcessConnections[slot].getServiceNumber();
109                     Log.e(TAG, "Unable to find connection to free in slot: " + slot +
110                             " already occupied by service: " + occupier);
111                     assert false;
112                 } else {
113                     mChildProcessConnections[slot] = null;
114                     assert !mFreeConnectionIndices.contains(slot);
115                     mFreeConnectionIndices.add(slot);
116                 }
117             }
118         }
119     }
120
121     // Service class for child process. As the default value it uses SandboxedProcessService0 and
122     // PrivilegedProcessService0.
123     private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator =
124             new ChildConnectionAllocator(true);
125     private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator =
126             new ChildConnectionAllocator(false);
127
128     private static boolean sConnectionAllocated = false;
129
130     // Sets service class for sandboxed service and privileged service.
131     public static void setChildProcessClass(
132             Class<? extends SandboxedProcessService> sandboxedServiceClass,
133             Class<? extends PrivilegedProcessService> privilegedServiceClass) {
134         // We should guarantee this is called before allocating connection.
135         assert !sConnectionAllocated;
136         sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass);
137         sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass);
138     }
139
140     private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) {
141         return inSandbox ?
142                 sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator;
143     }
144
145     private static ChildProcessConnection allocateConnection(Context context,
146             boolean inSandbox, LinkerParams linkerParams) {
147         ChildProcessConnection.DeathCallback deathCallback =
148             new ChildProcessConnection.DeathCallback() {
149                 @Override
150                 public void onChildProcessDied(int pid) {
151                     stop(pid);
152                 }
153             };
154         sConnectionAllocated = true;
155         return getConnectionAllocator(inSandbox).allocate(context, deathCallback, linkerParams);
156     }
157
158     private static boolean sLinkerInitialized = false;
159     private static long sLinkerLoadAddress = 0;
160
161     private static LinkerParams getLinkerParamsForNewConnection() {
162         if (!sLinkerInitialized) {
163             if (Linker.isUsed()) {
164                 sLinkerLoadAddress = Linker.getBaseLoadAddress();
165                 if (sLinkerLoadAddress == 0) {
166                     Log.i(TAG, "Shared RELRO support disabled!");
167                 }
168             }
169             sLinkerInitialized = true;
170         }
171
172         if (sLinkerLoadAddress == 0)
173             return null;
174
175         // Always wait for the shared RELROs in service processes.
176         final boolean waitForSharedRelros = true;
177         return new LinkerParams(sLinkerLoadAddress,
178                                 waitForSharedRelros,
179                                 Linker.getTestRunnerClassName());
180     }
181
182     private static ChildProcessConnection allocateBoundConnection(Context context,
183             String[] commandLine, boolean inSandbox) {
184         LinkerParams linkerParams = getLinkerParamsForNewConnection();
185         ChildProcessConnection connection = allocateConnection(context, inSandbox, linkerParams);
186         if (connection != null) {
187             connection.start(commandLine);
188         }
189         return connection;
190     }
191
192     private static void freeConnection(ChildProcessConnection connection) {
193         if (connection == null) {
194             return;
195         }
196         getConnectionAllocator(connection.isInSandbox()).free(connection);
197         return;
198     }
199
200     // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
201     private static final int NULL_PROCESS_HANDLE = 0;
202
203     // Map from pid to ChildService connection.
204     private static Map<Integer, ChildProcessConnection> sServiceMap =
205             new ConcurrentHashMap<Integer, ChildProcessConnection>();
206
207     // A pre-allocated and pre-bound connection ready for connection setup, or null.
208     private static ChildProcessConnection sSpareSandboxedConnection = null;
209
210     /**
211      * Manages oom bindings used to bound child services. "Oom binding" is a binding that raises the
212      * process oom priority so that it shouldn't be killed by the OS out-of-memory killer under
213      * normal conditions (it can still be killed under drastic memory pressure).
214      *
215      * This class serves a proxy between external calls that manipulate the bindings and the
216      * connections, allowing to enforce policies such as delayed removal of the bindings.
217      */
218     static class BindingManager {
219         // Delay of 1 second used when removing the initial oom binding of a process.
220         private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000;
221
222         // Delay of 5 second used when removing temporary strong binding of a process (only on
223         // non-low-memory devices).
224         private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 5 * 1000;
225
226         // Map from pid to the count of oom bindings bound for the service. Should be accessed with
227         // mCountLock.
228         private final SparseIntArray mOomBindingCount = new SparseIntArray();
229
230         // Pid of the renderer that was most recently oom bound. This is used on low-memory devices
231         // to drop oom bindings of a process when another one acquires them, making sure that only
232         // one renderer process at a time is oom bound. Should be accessed with mCountLock.
233         private int mLastOomPid = -1;
234
235         // Should be acquired before binding or unbinding the connections and modifying state
236         // variables: mOomBindingCount and mLastOomPid.
237         private final Object mCountLock = new Object();
238
239         /**
240          * Registers an oom binding bound for a child process. Should be called with mCountLock.
241          * @param pid handle of the process.
242          */
243         private void incrementOomCount(int pid) {
244             mOomBindingCount.put(pid, mOomBindingCount.get(pid) + 1);
245             mLastOomPid = pid;
246         }
247
248         /**
249          * Registers an oom binding unbound for a child process. Should be called with mCountLock.
250          * @param pid handle of the process.
251          */
252         private void decrementOomCount(int pid) {
253             int count = mOomBindingCount.get(pid, -1);
254             assert count > 0;
255             count--;
256             if (count > 0) {
257                 mOomBindingCount.put(pid, count);
258             } else {
259                 mOomBindingCount.delete(pid);
260             }
261         }
262
263         /**
264          * Drops all oom bindings for the given renderer.
265          * @param pid handle of the process.
266          */
267         private void dropOomBindings(int pid) {
268             ChildProcessConnection connection = sServiceMap.get(pid);
269             if (connection == null) {
270                 LogPidWarning(pid, "Tried to drop oom bindings for a non-existent connection");
271                 return;
272             }
273             synchronized (mCountLock) {
274                 connection.dropOomBindings();
275                 mOomBindingCount.delete(pid);
276             }
277         }
278
279         /**
280          * Registers a freshly started child process. On low-memory devices this will also drop the
281          * oom bindings of the last process that was oom-bound. We can do that, because every time a
282          * connection is created on the low-end, it is used in foreground (no prerendering, no
283          * loading of tabs opened in background).
284          * @param pid handle of the process.
285          */
286         void addNewConnection(int pid) {
287             synchronized (mCountLock) {
288                 if (SysUtils.isLowEndDevice() && mLastOomPid >= 0) {
289                     dropOomBindings(mLastOomPid);
290                 }
291                 // This will reset the previous entry for the pid in the unlikely event of the OS
292                 // reusing renderer pids.
293                 mOomBindingCount.put(pid, 0);
294                 // Every new connection is bound with initial oom binding.
295                 incrementOomCount(pid);
296             }
297         }
298
299         /**
300          * Remove the initial binding of the child process. Child processes are bound with initial
301          * binding to protect them from getting killed before they are put to use. This method
302          * allows to remove the binding once it is no longer needed. The binding is removed after a
303          * fixed delay period so that the renderer will not be killed immediately after the call.
304          */
305         void removeInitialBinding(final int pid) {
306             final ChildProcessConnection connection = sServiceMap.get(pid);
307             if (connection == null) {
308                 LogPidWarning(pid, "Tried to remove a binding for a non-existent connection");
309                 return;
310             }
311             if (!connection.isInitialBindingBound()) return;
312             ThreadUtils.postOnUiThreadDelayed(new Runnable() {
313                 @Override
314                 public void run() {
315                     synchronized (mCountLock) {
316                         if (connection.isInitialBindingBound()) {
317                             decrementOomCount(pid);
318                             connection.removeInitialBinding();
319                         }
320                     }
321                 }
322             }, REMOVE_INITIAL_BINDING_DELAY_MILLIS);
323         }
324
325         /**
326          * Bind a child process as a high priority process so that it has the same priority as the
327          * main process. This can be used for the foreground renderer process to distinguish it from
328          * the background renderer process.
329          * @param pid The process handle of the service connection.
330          */
331         void bindAsHighPriority(final int pid) {
332             ChildProcessConnection connection = sServiceMap.get(pid);
333             if (connection == null) {
334                 LogPidWarning(pid, "Tried to bind a non-existent connection");
335                 return;
336             }
337             synchronized (mCountLock) {
338                 connection.attachAsActive();
339                 incrementOomCount(pid);
340             }
341         }
342
343         /**
344          * Unbind a high priority process which was previous bound with bindAsHighPriority.
345          * @param pid The process handle of the service.
346          */
347         void unbindAsHighPriority(final int pid) {
348             final ChildProcessConnection connection = sServiceMap.get(pid);
349             if (connection == null) {
350                 LogPidWarning(pid, "Tried to unbind non-existent connection");
351                 return;
352             }
353             if (!connection.isStrongBindingBound()) return;
354
355             // This runnable performs the actual unbinding. It will be executed synchronously when
356             // on low-end devices and posted with a delay otherwise.
357             Runnable doUnbind = new Runnable() {
358                 @Override
359                 public void run() {
360                     synchronized (mCountLock) {
361                         if (connection.isStrongBindingBound()) {
362                             decrementOomCount(pid);
363                             connection.detachAsActive();
364                         }
365                     }
366                 }
367             };
368
369             if (SysUtils.isLowEndDevice()) {
370                 doUnbind.run();
371             } else {
372                 ThreadUtils.postOnUiThreadDelayed(doUnbind, DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS);
373             }
374         }
375
376         /**
377          * @return True iff the given service process is protected from the out-of-memory killing,
378          * or it was protected when it died (either crashed or was closed). This can be used to
379          * decide if a disconnection of a renderer was a crash or a probable out-of-memory kill. In
380          * the unlikely event of the OS reusing renderer pid, the call will refer to the most recent
381          * renderer of the given pid. The binding count is being reset in addNewConnection().
382          */
383         boolean isOomProtected(int pid) {
384             synchronized (mCountLock) {
385                 return mOomBindingCount.get(pid) > 0;
386             }
387         }
388     }
389
390     private static BindingManager sBindingManager = new BindingManager();
391
392     static BindingManager getBindingManager() {
393         return sBindingManager;
394     }
395
396     /**
397      * Returns the child process service interface for the given pid. This may be called on
398      * any thread, but the caller must assume that the service can disconnect at any time. All
399      * service calls should catch and handle android.os.RemoteException.
400      *
401      * @param pid The pid (process handle) of the service obtained from {@link #start}.
402      * @return The IChildProcessService or null if the service no longer exists.
403      */
404     public static IChildProcessService getChildService(int pid) {
405         ChildProcessConnection connection = sServiceMap.get(pid);
406         if (connection != null) {
407             return connection.getService();
408         }
409         return null;
410     }
411
412     /**
413      * Should be called early in startup so the work needed to spawn the child process can be done
414      * in parallel to other startup work. Must not be called on the UI thread. Spare connection is
415      * created in sandboxed child process.
416      * @param context the application context used for the connection.
417      */
418     public static void warmUp(Context context) {
419         synchronized (ChildProcessLauncher.class) {
420             assert !ThreadUtils.runningOnUiThread();
421             if (sSpareSandboxedConnection == null) {
422                 sSpareSandboxedConnection = allocateBoundConnection(context, null, true);
423             }
424         }
425     }
426
427     private static String getSwitchValue(final String[] commandLine, String switchKey) {
428         if (commandLine == null || switchKey == null) {
429             return null;
430         }
431         // This format should be matched with the one defined in command_line.h.
432         final String switchKeyPrefix = "--" + switchKey + "=";
433         for (String command : commandLine) {
434             if (command != null && command.startsWith(switchKeyPrefix)) {
435                 return command.substring(switchKeyPrefix.length());
436             }
437         }
438         return null;
439     }
440
441     /**
442      * Spawns and connects to a child process. May be called on any thread. It will not block, but
443      * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is
444      * established. Note this callback will not necessarily be from the same thread (currently it
445      * always comes from the main thread).
446      *
447      * @param context Context used to obtain the application context.
448      * @param commandLine The child process command line argv.
449      * @param file_ids The ID that should be used when mapping files in the created process.
450      * @param file_fds The file descriptors that should be mapped in the created process.
451      * @param file_auto_close Whether the file descriptors should be closed once they were passed to
452      * the created process.
453      * @param clientContext Arbitrary parameter used by the client to distinguish this connection.
454      */
455     @CalledByNative
456     static void start(
457             Context context,
458             final String[] commandLine,
459             int[] fileIds,
460             int[] fileFds,
461             boolean[] fileAutoClose,
462             final int clientContext) {
463         assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length;
464         FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length];
465         for (int i = 0; i < fileFds.length; i++) {
466             filesToBeMapped[i] =
467                     new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]);
468         }
469         assert clientContext != 0;
470
471         int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS;
472         boolean inSandbox = true;
473         String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE);
474         if (SWITCH_RENDERER_PROCESS.equals(processType)) {
475             callbackType = CALLBACK_FOR_RENDERER_PROCESS;
476         } else if (SWITCH_GPU_PROCESS.equals(processType)) {
477             callbackType = CALLBACK_FOR_GPU_PROCESS;
478         } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) {
479             inSandbox = false;
480         }
481
482         ChildProcessConnection allocatedConnection = null;
483         synchronized (ChildProcessLauncher.class) {
484             if (inSandbox) {
485                 allocatedConnection = sSpareSandboxedConnection;
486                 sSpareSandboxedConnection = null;
487             }
488         }
489         if (allocatedConnection == null) {
490             allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox);
491             if (allocatedConnection == null) {
492                 // Notify the native code so it can free the heap allocated callback.
493                 nativeOnChildProcessStarted(clientContext, 0);
494                 return;
495             }
496         }
497         final ChildProcessConnection connection = allocatedConnection;
498         Log.d(TAG, "Setting up connection to process: slot=" + connection.getServiceNumber());
499
500         ChildProcessConnection.ConnectionCallback connectionCallback =
501                 new ChildProcessConnection.ConnectionCallback() {
502             public void onConnected(int pid) {
503                 Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext);
504                 if (pid != NULL_PROCESS_HANDLE) {
505                     sBindingManager.addNewConnection(pid);
506                     sServiceMap.put(pid, connection);
507                 } else {
508                     freeConnection(connection);
509                 }
510                 nativeOnChildProcessStarted(clientContext, pid);
511             }
512         };
513
514         // TODO(sievers): Revisit this as it doesn't correctly handle the utility process
515         // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS;
516
517         connection.setupConnection(commandLine,
518                                    filesToBeMapped,
519                                    createCallback(callbackType),
520                                    connectionCallback,
521                                    Linker.getSharedRelros());
522     }
523
524     /**
525      * Terminates a child process. This may be called from any thread.
526      *
527      * @param pid The pid (process handle) of the service connection obtained from {@link #start}.
528      */
529     @CalledByNative
530     static void stop(int pid) {
531         Log.d(TAG, "stopping child connection: pid=" + pid);
532         ChildProcessConnection connection = sServiceMap.remove(pid);
533         if (connection == null) {
534             LogPidWarning(pid, "Tried to stop non-existent connection");
535             return;
536         }
537         connection.stop();
538         freeConnection(connection);
539     }
540
541     /**
542      * This implementation is used to receive callbacks from the remote service.
543      */
544     private static IChildProcessCallback createCallback(final int callbackType) {
545         return new IChildProcessCallback.Stub() {
546             /**
547              * This is called by the remote service regularly to tell us about new values. Note that
548              * IPC calls are dispatched through a thread pool running in each process, so the code
549              * executing here will NOT be running in our main thread -- so, to update the UI, we
550              * need to use a Handler.
551              */
552             @Override
553             public void establishSurfacePeer(
554                     int pid, Surface surface, int primaryID, int secondaryID) {
555                 // Do not allow a malicious renderer to connect to a producer. This is only used
556                 // from stream textures managed by the GPU process.
557                 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
558                     Log.e(TAG, "Illegal callback for non-GPU process.");
559                     return;
560                 }
561
562                 nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID);
563             }
564
565             @Override
566             public Surface getViewSurface(int surfaceId) {
567                 // Do not allow a malicious renderer to get to our view surface.
568                 if (callbackType != CALLBACK_FOR_GPU_PROCESS) {
569                     Log.e(TAG, "Illegal callback for non-GPU process.");
570                     return null;
571                 }
572
573                 return nativeGetViewSurface(surfaceId);
574             }
575         };
576     };
577
578     private static void LogPidWarning(int pid, String message) {
579         // This class is effectively a no-op in single process mode, so don't log warnings there.
580         if (pid > 0 && !nativeIsSingleProcess()) {
581             Log.w(TAG, message + ", pid=" + pid);
582         }
583     }
584
585     private static native void nativeOnChildProcessStarted(int clientContext, int pid);
586     private static native Surface nativeGetViewSurface(int surfaceId);
587     private static native void nativeEstablishSurfacePeer(
588             int pid, Surface surface, int primaryID, int secondaryID);
589     private static native boolean nativeIsSingleProcess();
590 }