1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.gecko.process;
6 
7 import org.mozilla.gecko.GeckoAppShell;
8 import org.mozilla.gecko.GeckoNetworkManager;
9 import org.mozilla.gecko.TelemetryUtils;
10 import org.mozilla.gecko.GeckoThread;
11 import org.mozilla.gecko.IGeckoEditableChild;
12 import org.mozilla.gecko.IGeckoEditableParent;
13 import org.mozilla.gecko.annotation.WrapForJNI;
14 import org.mozilla.gecko.mozglue.JNIObject;
15 import org.mozilla.gecko.process.ServiceAllocator.PriorityLevel;
16 import org.mozilla.gecko.util.ThreadUtils;
17 import org.mozilla.gecko.util.XPCOMEventTarget;
18 
19 import org.mozilla.geckoview.GeckoResult;
20 
21 import android.os.Bundle;
22 import android.os.DeadObjectException;
23 import android.os.IBinder;
24 import android.os.ParcelFileDescriptor;
25 import android.os.RemoteException;
26 import androidx.annotation.NonNull;
27 import androidx.collection.ArrayMap;
28 import androidx.collection.ArraySet;
29 import androidx.collection.SimpleArrayMap;
30 import android.util.Log;
31 
32 import java.util.UUID;
33 
34 
35 public final class GeckoProcessManager extends IProcessManager.Stub {
36     private static final String LOGTAG = "GeckoProcessManager";
37     private static final GeckoProcessManager INSTANCE = new GeckoProcessManager();
38     private static final int INVALID_PID = 0;
39 
40     // This id univocally identifies the current process manager instance
41     private final String mInstanceId;
42 
getInstance()43     public static GeckoProcessManager getInstance() {
44         return INSTANCE;
45     }
46 
47     @WrapForJNI(calledFrom = "gecko")
setEditableChildParent(final IGeckoEditableChild child, final IGeckoEditableParent parent)48     private static void setEditableChildParent(final IGeckoEditableChild child,
49                                                final IGeckoEditableParent parent) {
50         try {
51             child.transferParent(parent);
52         } catch (final RemoteException e) {
53             Log.e(LOGTAG, "Cannot set parent", e);
54         }
55     }
56 
57     @WrapForJNI(stubName = "GetEditableParent", dispatchTo = "gecko")
nativeGetEditableParent(IGeckoEditableChild child, long contentId, long tabId)58     private static native void nativeGetEditableParent(IGeckoEditableChild child,
59                                                        long contentId, long tabId);
60 
61     @Override // IProcessManager
getEditableParent(final IGeckoEditableChild child, final long contentId, final long tabId)62     public void getEditableParent(final IGeckoEditableChild child,
63                                   final long contentId, final long tabId) {
64         nativeGetEditableParent(child, contentId, tabId);
65     }
66 
67     /**
68      * Gecko uses this class to uniquely identify a process managed by GeckoProcessManager.
69      */
70     public static final class Selector {
71         private final GeckoProcessType mType;
72         private final int mPid;
73 
74         @WrapForJNI
Selector(@onNull final GeckoProcessType type, final int pid)75         private Selector(@NonNull final GeckoProcessType type, final int pid) {
76             if (pid == INVALID_PID) {
77                 throw new RuntimeException("Invalid PID");
78             }
79 
80             mType = type;
81             mPid = pid;
82         }
83 
84         @WrapForJNI
Selector(@onNull final GeckoProcessType type)85         private Selector(@NonNull final GeckoProcessType type) {
86             mType = type;
87             mPid = INVALID_PID;
88         }
89 
getType()90         public GeckoProcessType getType() {
91             return mType;
92         }
93 
getPid()94         public int getPid() {
95             return mPid;
96         }
97 
98         @Override
equals(final Object obj)99         public boolean equals(final Object obj) {
100             if (obj == null) {
101                 return false;
102             }
103 
104             if (obj == ((Object)this)) {
105                 return true;
106             }
107 
108             final Selector other = (Selector) obj;
109             return mType == other.mType && mPid == other.mPid;
110         }
111 
112         @Override
hashCode()113         public int hashCode() {
114             return 31 * mType.hashCode() + mPid;
115         }
116     }
117 
118     private static final class IncompleteChildConnectionException extends RuntimeException {
IncompleteChildConnectionException(@onNull final String msg)119         public IncompleteChildConnectionException(@NonNull final String msg) {
120             super(msg);
121         }
122     }
123 
124     /**
125      * Maintains state pertaining to an individual child process. Inheriting from
126      * ServiceAllocator.InstanceInfo enables this class to work with ServiceAllocator.
127      */
128     private static class ChildConnection extends ServiceAllocator.InstanceInfo {
129         private IChildProcess mChild;
130         private GeckoResult<IChildProcess> mPendingBind;
131         private int mPid;
132 
ChildConnection(@onNull final ServiceAllocator allocator, @NonNull final GeckoProcessType type, @NonNull final PriorityLevel initialPriority)133         protected ChildConnection(@NonNull final ServiceAllocator allocator,
134                                   @NonNull final GeckoProcessType type,
135                                   @NonNull final PriorityLevel initialPriority) {
136             super(allocator, type, initialPriority);
137             mPid = INVALID_PID;
138         }
139 
getPid()140         public int getPid() {
141             XPCOMEventTarget.assertOnLauncherThread();
142             if (mChild == null) {
143                 throw new IncompleteChildConnectionException("Calling ChildConnection.getPid() on an incomplete connection");
144             }
145 
146             return mPid;
147         }
148 
completeFailedBind(@onNull final ServiceAllocator.BindException e)149         private GeckoResult<IChildProcess> completeFailedBind(@NonNull final ServiceAllocator.BindException e) {
150             XPCOMEventTarget.assertOnLauncherThread();
151             Log.e(LOGTAG, "Failed bind", e);
152 
153             if (mPendingBind == null) {
154                 throw new IllegalStateException("Bind failed with null mPendingBind");
155             }
156 
157             final GeckoResult<IChildProcess> bindResult = mPendingBind;
158             mPendingBind = null;
159             unbind().accept(v -> bindResult.completeExceptionally(e));
160             return bindResult;
161         }
162 
bind()163         public GeckoResult<IChildProcess> bind() {
164             XPCOMEventTarget.assertOnLauncherThread();
165 
166             if (mChild != null) {
167                 // Already bound
168                 return GeckoResult.fromValue(mChild);
169             }
170 
171             if (mPendingBind != null) {
172                 // Bind in progress
173                 return mPendingBind;
174             }
175 
176             mPendingBind = new GeckoResult<>();
177             try {
178                 if (!bindService()) {
179                     throw new ServiceAllocator.BindException("Cannot connect to process");
180                 }
181             } catch (final ServiceAllocator.BindException e) {
182                 return completeFailedBind(e);
183             }
184 
185             return mPendingBind;
186         }
187 
unbind()188         public GeckoResult<Void> unbind() {
189             XPCOMEventTarget.assertOnLauncherThread();
190 
191             if (mPendingBind != null) {
192                 // We called unbind() while bind() was still pending completion
193                 return mPendingBind.then(child -> unbind());
194             }
195 
196             if (mChild == null) {
197                 // Not bound in the first place
198                 return GeckoResult.fromValue(null);
199             }
200 
201             unbindService();
202 
203             return GeckoResult.fromValue(null);
204         }
205 
206         @Override
onBinderConnected(final IBinder service)207         protected void onBinderConnected(final IBinder service) {
208             XPCOMEventTarget.assertOnLauncherThread();
209 
210             final IChildProcess child = IChildProcess.Stub.asInterface(service);
211             try {
212                 mPid = child.getPid();
213             } catch (final DeadObjectException e) {
214                 unbindService();
215 
216                 // mPendingBind might be null if a bind was initiated by the system (eg Service Restart)
217                 if (mPendingBind != null) {
218                     mPendingBind.completeExceptionally(e);
219                     mPendingBind = null;
220                 }
221 
222                 return;
223             } catch (final RemoteException e) {
224                 throw new RuntimeException(e);
225             }
226 
227             mChild = child;
228             GeckoProcessManager.INSTANCE.mConnections.onBindComplete(this);
229 
230             // mPendingBind might be null if a bind was initiated by the system (eg Service Restart)
231             if (mPendingBind != null) {
232                 mPendingBind.complete(mChild);
233                 mPendingBind = null;
234             }
235         }
236 
237         @Override
onReleaseResources()238         protected void onReleaseResources() {
239             XPCOMEventTarget.assertOnLauncherThread();
240 
241             // NB: This must happen *before* resetting mPid!
242             GeckoProcessManager.INSTANCE.mConnections.removeConnection(this);
243 
244             mChild = null;
245             mPid = INVALID_PID;
246         }
247     }
248 
249     private static class NonContentConnection extends ChildConnection {
NonContentConnection(@onNull final ServiceAllocator allocator, @NonNull final GeckoProcessType type)250         public NonContentConnection(@NonNull final ServiceAllocator allocator,
251                                     @NonNull final GeckoProcessType type) {
252             super(allocator, type, PriorityLevel.FOREGROUND);
253             if (type == GeckoProcessType.CONTENT) {
254                 throw new AssertionError("Attempt to create a NonContentConnection as CONTENT");
255             }
256         }
257 
onAppForeground()258         protected void onAppForeground() {
259             setPriorityLevel(PriorityLevel.FOREGROUND);
260         }
261 
onAppBackground()262         protected void onAppBackground() {
263             setPriorityLevel(PriorityLevel.BACKGROUND);
264         }
265     }
266 
267     private static final class SocketProcessConnection extends NonContentConnection {
268         private boolean mIsForeground = true;
269         private boolean mIsNetworkUp = true;
270 
SocketProcessConnection(@onNull final ServiceAllocator allocator)271         public SocketProcessConnection(@NonNull final ServiceAllocator allocator) {
272             super(allocator, GeckoProcessType.SOCKET);
273             GeckoProcessManager.INSTANCE.mConnections.enableNetworkNotifications();
274         }
275 
onNetworkStateChange(final boolean isNetworkUp)276         public void onNetworkStateChange(final boolean isNetworkUp) {
277             mIsNetworkUp = isNetworkUp;
278             prioritize();
279         }
280 
281         @Override
onAppForeground()282         protected void onAppForeground() {
283             mIsForeground = true;
284             prioritize();
285         }
286 
287         @Override
onAppBackground()288         protected void onAppBackground() {
289             mIsForeground = false;
290             prioritize();
291         }
292 
293         private static final PriorityLevel[][] sPriorityStates = initPriorityStates();
294 
initPriorityStates()295         private static PriorityLevel[][] initPriorityStates() {
296             final PriorityLevel[][] states = new PriorityLevel[2][2];
297             // Background, no network
298             states[0][0] = PriorityLevel.IDLE;
299             // Background, network
300             states[0][1] = PriorityLevel.BACKGROUND;
301             // Foreground, no network
302             states[1][0] = PriorityLevel.IDLE;
303             // Foreground, network
304             states[1][1] = PriorityLevel.FOREGROUND;
305             return states;
306         }
307 
prioritize()308         private void prioritize() {
309             final PriorityLevel nextPriority = sPriorityStates[mIsForeground ? 1 : 0][mIsNetworkUp ? 1 : 0];
310             setPriorityLevel(nextPriority);
311         }
312     }
313 
314     private static final class ContentConnection extends ChildConnection {
315         private static final String TELEMETRY_PROCESS_LIFETIME_HISTOGRAM_NAME = "GV_CONTENT_PROCESS_LIFETIME_MS";
316 
317         private TelemetryUtils.UptimeTimer mLifetimeTimer = null;
318 
ContentConnection(@onNull final ServiceAllocator allocator, @NonNull final PriorityLevel initialPriority)319         public ContentConnection(@NonNull final ServiceAllocator allocator,
320                                  @NonNull final PriorityLevel initialPriority) {
321             super(allocator, GeckoProcessType.CONTENT, initialPriority);
322         }
323 
324         @Override
onBinderConnected(final IBinder service)325         protected void onBinderConnected(final IBinder service) {
326             mLifetimeTimer = new TelemetryUtils.UptimeTimer(TELEMETRY_PROCESS_LIFETIME_HISTOGRAM_NAME);
327             super.onBinderConnected(service);
328         }
329 
330         @Override
onReleaseResources()331         protected void onReleaseResources() {
332             if (mLifetimeTimer != null) {
333                 mLifetimeTimer.stop();
334                 mLifetimeTimer = null;
335             }
336 
337             super.onReleaseResources();
338         }
339     }
340 
341     /**
342      * This class manages the state surrounding existing connections and their priorities.
343      */
344     private static final class ConnectionManager extends JNIObject {
345         // Connections to non-content processes
346         private final ArrayMap<GeckoProcessType, NonContentConnection> mNonContentConnections;
347         // Mapping of pid to content process
348         private final SimpleArrayMap<Integer, ContentConnection> mContentPids;
349         // Set of initialized content process connections
350         private final ArraySet<ContentConnection> mContentConnections;
351         // Set of bound but uninitialized content connections
352         private final ArraySet<ContentConnection> mNonStartedContentConnections;
353         // Allocator for service IDs
354         private final ServiceAllocator mServiceAllocator;
355         private boolean mIsObservingNetwork = false;
356 
ConnectionManager()357         public ConnectionManager() {
358             mNonContentConnections = new ArrayMap<GeckoProcessType, NonContentConnection>();
359             mContentPids = new SimpleArrayMap<Integer, ContentConnection>();
360             mContentConnections = new ArraySet<ContentConnection>();
361             mNonStartedContentConnections = new ArraySet<ContentConnection>();
362             mServiceAllocator = new ServiceAllocator();
363 
364             // Attach to native once JNI is ready.
365             if (GeckoThread.isStateAtLeast(GeckoThread.State.JNI_READY)) {
366                 attachTo(this);
367             } else {
368                 GeckoThread.queueNativeCallUntil(GeckoThread.State.JNI_READY, ConnectionManager.class,
369                                                  "attachTo", this);
370             }
371         }
372 
enableNetworkNotifications()373         private void enableNetworkNotifications() {
374             if (mIsObservingNetwork) {
375                 return;
376             }
377 
378             mIsObservingNetwork = true;
379 
380             // Ensure that GeckoNetworkManager is monitoring network events so that we can
381             // prioritize the socket process.
382             ThreadUtils.runOnUiThread(() -> {
383                 GeckoNetworkManager.getInstance().enableNotifications();
384             });
385 
386             observeNetworkNotifications();
387         }
388 
389         @WrapForJNI(dispatchTo = "gecko")
attachTo(ConnectionManager instance)390         private static native void attachTo(ConnectionManager instance);
391 
392         @WrapForJNI(dispatchTo = "gecko")
observeNetworkNotifications()393         private native void observeNetworkNotifications();
394 
395         @WrapForJNI(calledFrom = "gecko")
onBackground()396         private void onBackground() {
397             XPCOMEventTarget.runOnLauncherThread(() -> onAppBackgroundInternal());
398         }
399 
400         @WrapForJNI(calledFrom = "gecko")
onForeground()401         private void onForeground() {
402             XPCOMEventTarget.runOnLauncherThread(() -> onAppForegroundInternal());
403         }
404 
405         @WrapForJNI(calledFrom = "gecko")
onNetworkStateChange(final boolean isUp)406         private void onNetworkStateChange(final boolean isUp) {
407             XPCOMEventTarget.runOnLauncherThread(() -> onNetworkStateChangeInternal(isUp));
408         }
409 
410         @Override
disposeNative()411         protected native void disposeNative();
412 
onAppBackgroundInternal()413         private void onAppBackgroundInternal() {
414             XPCOMEventTarget.assertOnLauncherThread();
415 
416             for (final NonContentConnection conn : mNonContentConnections.values()) {
417                 conn.onAppBackground();
418             }
419         }
420 
onAppForegroundInternal()421         private void onAppForegroundInternal() {
422             XPCOMEventTarget.assertOnLauncherThread();
423 
424             for (final NonContentConnection conn : mNonContentConnections.values()) {
425                 conn.onAppForeground();
426             }
427         }
428 
onNetworkStateChangeInternal(final boolean isUp)429         private void onNetworkStateChangeInternal(final boolean isUp) {
430             XPCOMEventTarget.assertOnLauncherThread();
431 
432             final SocketProcessConnection conn = (SocketProcessConnection) mNonContentConnections.get(GeckoProcessType.SOCKET);
433             if (conn == null) {
434                 return;
435             }
436 
437             conn.onNetworkStateChange(isUp);
438         }
439 
removeContentConnection(@onNull final ChildConnection conn)440         private void removeContentConnection(@NonNull final ChildConnection conn) {
441             if (!mContentConnections.remove(conn)) {
442                 throw new RuntimeException("Attempt to remove non-registered connection");
443             }
444             mNonStartedContentConnections.remove(conn);
445 
446             final int pid;
447 
448             try {
449                 pid = conn.getPid();
450             } catch (final IncompleteChildConnectionException e) {
451                 // conn lost its binding before it was able to retrieve its pid. It follows that
452                 // mContentPids does not have an entry for this connection, so we can just return.
453                 return;
454             }
455 
456             if (pid == INVALID_PID) {
457                 return;
458             }
459 
460             final ChildConnection removed = mContentPids.remove(Integer.valueOf(pid));
461             if (removed != null && removed != conn) {
462                 throw new RuntimeException("Integrity error - connection mismatch for pid " + Integer.toString(pid));
463             }
464         }
465 
removeConnection(@onNull final ChildConnection conn)466         public void removeConnection(@NonNull final ChildConnection conn) {
467             XPCOMEventTarget.assertOnLauncherThread();
468 
469             if (conn.getType() == GeckoProcessType.CONTENT) {
470                 removeContentConnection(conn);
471                 return;
472             }
473 
474             final ChildConnection removed = mNonContentConnections.remove(conn.getType());
475             if (removed != conn) {
476                 throw new RuntimeException("Integrity error - connection mismatch for process type " + conn.getType().toString());
477             }
478         }
479 
480         /**
481          * Saves any state information that was acquired upon start completion.
482          */
onBindComplete(@onNull final ChildConnection conn)483         public void onBindComplete(@NonNull final ChildConnection conn) {
484             if (conn.getType() == GeckoProcessType.CONTENT) {
485                 final int pid = conn.getPid();
486                 if (pid == INVALID_PID) {
487                     throw new AssertionError("PID is invalid even though our caller just successfully retrieved it after binding");
488                 }
489 
490                 mContentPids.put(Integer.valueOf(pid), (ContentConnection) conn);
491             }
492         }
493 
494         /**
495          * Retrieve the ChildConnection for an already running content process.
496          */
getExistingContentConnection(@onNull final Selector selector)497         private ContentConnection getExistingContentConnection(@NonNull final Selector selector) {
498             XPCOMEventTarget.assertOnLauncherThread();
499             if (selector.getType() != GeckoProcessType.CONTENT) {
500                 throw new IllegalArgumentException("Selector is not for content!");
501             }
502 
503             return mContentPids.get(Integer.valueOf(selector.getPid()));
504         }
505 
506         /**
507          * Unconditionally create a new content connection for the specified priority.
508          */
getNewContentConnection(@onNull final PriorityLevel newPriority)509         private ContentConnection getNewContentConnection(@NonNull final PriorityLevel newPriority) {
510             final ContentConnection result = new ContentConnection(mServiceAllocator, newPriority);
511             mContentConnections.add(result);
512 
513             return result;
514         }
515 
516         /**
517          * Retrieve the ChildConnection for an already running child process of any type.
518          */
getExistingConnection(@onNull final Selector selector)519         public ChildConnection getExistingConnection(@NonNull final Selector selector) {
520             XPCOMEventTarget.assertOnLauncherThread();
521 
522             final GeckoProcessType type = selector.getType();
523 
524             if (type == GeckoProcessType.CONTENT) {
525                 return getExistingContentConnection(selector);
526             }
527 
528             return mNonContentConnections.get(type);
529         }
530 
531         /**
532          * Retrieve a ChildConnection for a content process for the purposes of starting. If there
533          * are any preloaded content processes already running, we will use one of those.
534          * Otherwise we will allocate a new ChildConnection.
535          */
getContentConnectionForStart()536         private ChildConnection getContentConnectionForStart() {
537             XPCOMEventTarget.assertOnLauncherThread();
538 
539             if (mNonStartedContentConnections.isEmpty()) {
540                 return getNewContentConnection(PriorityLevel.FOREGROUND);
541             }
542 
543             final ChildConnection conn = mNonStartedContentConnections.removeAt(mNonStartedContentConnections.size() - 1);
544             conn.setPriorityLevel(PriorityLevel.FOREGROUND);
545             return conn;
546         }
547 
548         /**
549          * Retrieve or create a new child process for the specified non-content process.
550          */
getNonContentConnection(@onNull final GeckoProcessType type)551         private ChildConnection getNonContentConnection(@NonNull final GeckoProcessType type) {
552             XPCOMEventTarget.assertOnLauncherThread();
553             if (type == GeckoProcessType.CONTENT) {
554                 throw new IllegalArgumentException("Content processes not supported by this method");
555             }
556 
557             NonContentConnection connection = mNonContentConnections.get(type);
558             if (connection == null) {
559                 if (type == GeckoProcessType.SOCKET) {
560                     connection = new SocketProcessConnection(mServiceAllocator);
561                 } else {
562                     connection = new NonContentConnection(mServiceAllocator, type);
563                 }
564 
565                 mNonContentConnections.put(type, connection);
566             }
567 
568             return connection;
569         }
570 
571         /**
572          * Retrieve a ChildConnection for the purposes of starting a new child process.
573          */
getConnectionForStart(@onNull final GeckoProcessType type)574         public ChildConnection getConnectionForStart(@NonNull final GeckoProcessType type) {
575             if (type == GeckoProcessType.CONTENT) {
576                 return getContentConnectionForStart();
577             }
578 
579             return getNonContentConnection(type);
580         }
581 
582         /**
583          * Retrieve a ChildConnection for the purposes of preloading a new child process.
584          */
getConnectionForPreload(@onNull final GeckoProcessType type)585         public ChildConnection getConnectionForPreload(@NonNull final GeckoProcessType type) {
586             if (type == GeckoProcessType.CONTENT) {
587                 final ContentConnection conn = getNewContentConnection(PriorityLevel.BACKGROUND);
588                 mNonStartedContentConnections.add(conn);
589                 return conn;
590             }
591 
592             return getNonContentConnection(type);
593         }
594     }
595 
596     private final ConnectionManager mConnections;
597 
GeckoProcessManager()598     private GeckoProcessManager() {
599         mConnections = new ConnectionManager();
600         mInstanceId = UUID.randomUUID().toString();
601     }
602 
preload(final GeckoProcessType... types)603     public void preload(final GeckoProcessType... types) {
604         XPCOMEventTarget.launcherThread().execute(() -> {
605             for (final GeckoProcessType type : types) {
606                 final ChildConnection connection = mConnections.getConnectionForPreload(type);
607                 connection.bind();
608             }
609         });
610     }
611 
crashChild(@onNull final Selector selector)612     public void crashChild(@NonNull final Selector selector) {
613         XPCOMEventTarget.launcherThread().execute(() -> {
614             final ChildConnection conn = mConnections.getExistingConnection(selector);
615             if (conn == null) {
616                 return;
617             }
618 
619             conn.bind().accept(proc -> {
620                 try {
621                     proc.crash();
622                 } catch (final RemoteException e) {
623                 }
624             });
625         });
626     }
627 
628     @WrapForJNI
shutdownProcess(final Selector selector)629     private static void shutdownProcess(final Selector selector) {
630         XPCOMEventTarget.assertOnLauncherThread();
631         final ChildConnection conn = INSTANCE.mConnections.getExistingConnection(selector);
632         if (conn == null) {
633             return;
634         }
635 
636         conn.unbind();
637     }
638 
639     @WrapForJNI
setProcessPriority(@onNull final Selector selector, @NonNull final PriorityLevel priorityLevel, final int relativeImportance)640     private static void setProcessPriority(@NonNull final Selector selector,
641                                            @NonNull final PriorityLevel priorityLevel,
642                                            final int relativeImportance) {
643         XPCOMEventTarget.runOnLauncherThread(() -> {
644             final ChildConnection conn = INSTANCE.mConnections.getExistingConnection(selector);
645             if (conn == null) {
646                 return;
647             }
648 
649             conn.setPriorityLevel(priorityLevel, relativeImportance);
650         });
651     }
652 
653     @WrapForJNI
start(final GeckoProcessType type, final String[] args, final int prefsFd, final int prefMapFd, final int ipcFd, final int crashFd, final int crashAnnotationFd)654     private static GeckoResult<Integer> start(final GeckoProcessType type,
655                                               final String[] args,
656                                               final int prefsFd,
657                                               final int prefMapFd,
658                                               final int ipcFd,
659                                               final int crashFd,
660                                               final int crashAnnotationFd) {
661         final GeckoResult<Integer> result = new GeckoResult<>();
662         final Bundle extras = GeckoThread.getActiveExtras();
663         final int flags = filterFlagsForChild(GeckoThread.getActiveFlags());
664 
665         XPCOMEventTarget.runOnLauncherThread(() -> {
666             INSTANCE.start(result, type, args, extras, flags, prefsFd,
667                            prefMapFd, ipcFd, crashFd, crashAnnotationFd,
668                            /* isRetry */ false);
669         });
670 
671         return result;
672     }
673 
filterFlagsForChild(final int flags)674     private static int filterFlagsForChild(final int flags) {
675         return flags & GeckoThread.FLAG_ENABLE_NATIVE_CRASHREPORTER;
676     }
677 
start(final GeckoResult<Integer> result, final GeckoProcessType type, final String[] args, final Bundle extras, final int flags, final int prefsFd, final int prefMapFd, final int ipcFd, final int crashFd, final int crashAnnotationFd, final boolean isRetry)678     private void start(final GeckoResult<Integer> result, final GeckoProcessType type,
679                        final String[] args, final Bundle extras, final int flags,
680                        final int prefsFd, final int prefMapFd, final int ipcFd,
681                        final int crashFd, final int crashAnnotationFd,
682                        final boolean isRetry) {
683         start(result, type, args, extras, flags, prefsFd, prefMapFd, ipcFd,
684               crashFd, crashAnnotationFd, isRetry, /* prevException */ null);
685     }
686 
start(final GeckoResult<Integer> result, final GeckoProcessType type, final String[] args, final Bundle extras, final int flags, final int prefsFd, final int prefMapFd, final int ipcFd, final int crashFd, final int crashAnnotationFd, final boolean isRetry, final RemoteException prevException)687     private void start(final GeckoResult<Integer> result, final GeckoProcessType type,
688                        final String[] args, final Bundle extras, final int flags,
689                        final int prefsFd, final int prefMapFd, final int ipcFd,
690                        final int crashFd, final int crashAnnotationFd,
691                        final boolean isRetry,
692                        final RemoteException prevException) {
693         XPCOMEventTarget.assertOnLauncherThread();
694 
695         final ChildConnection connection = mConnections.getConnectionForStart(type);
696         final GeckoResult<IChildProcess> childResult = connection.bind();
697 
698         childResult.accept(childProcess -> {
699             start(result, connection, childProcess, type, args, extras,
700                   flags, prefsFd, prefMapFd, ipcFd, crashFd,
701                   crashAnnotationFd, isRetry, prevException);
702         }, error -> {
703                 final StringBuilder builder = new StringBuilder("Cannot bind child process: ");
704                 builder.append(error.toString());
705                 if (prevException != null) {
706                     builder.append("; Previous exception: ");
707                     builder.append(prevException.toString());
708                 }
709 
710                 builder.append("; Type: ");
711                 builder.append(type.toString());
712 
713                 final RuntimeException exception = new RuntimeException(builder.toString(), error);
714                 Log.e(LOGTAG, "Could not bind to process", exception);
715                 result.completeExceptionally(exception);
716             });
717     }
718 
acceptUnbindFailure(@onNull final GeckoResult<Void> unbindResult, @NonNull final GeckoResult<Integer> finalResult, final RemoteException exception, @NonNull final GeckoProcessType type, final boolean isRetry)719     private void acceptUnbindFailure(@NonNull final GeckoResult<Void> unbindResult,
720                                      @NonNull final GeckoResult<Integer> finalResult,
721                                      final RemoteException exception,
722                                      @NonNull final GeckoProcessType type,
723                                      final boolean isRetry) {
724         unbindResult.accept(null, error -> {
725             final StringBuilder builder = new StringBuilder("Failed to unbind");
726             if (isRetry) {
727                 builder.append(": ");
728             } else {
729                 builder.append(" before child restart: ");
730             }
731 
732             builder.append(error.toString());
733             if (exception != null) {
734                 builder.append("; In response to RemoteException: ");
735                 builder.append(exception.toString());
736             }
737 
738             builder.append("; Type = ");
739             builder.append(type.toString());
740 
741             finalResult.completeExceptionally(new RuntimeException(builder.toString()));
742         });
743     }
744 
start(final GeckoResult<Integer> result, final ChildConnection connection, final IChildProcess child, final GeckoProcessType type, final String[] args, final Bundle extras, final int flags, final int prefsFd, final int prefMapFd, final int ipcFd, final int crashFd, final int crashAnnotationFd, final boolean isRetry, final RemoteException prevException)745     private void start(final GeckoResult<Integer> result,
746                        final ChildConnection connection,
747                        final IChildProcess child,
748                        final GeckoProcessType type, final String[] args,
749                        final Bundle extras, final int flags,
750                        final int prefsFd, final int prefMapFd,
751                        final int ipcFd, final int crashFd,
752                        final int crashAnnotationFd, final boolean isRetry,
753                        final RemoteException prevException) {
754         XPCOMEventTarget.assertOnLauncherThread();
755 
756         final ParcelFileDescriptor prefsPfd =
757                 (prefsFd >= 0) ? ParcelFileDescriptor.adoptFd(prefsFd) : null;
758         final ParcelFileDescriptor prefMapPfd =
759                 (prefMapFd >= 0) ? ParcelFileDescriptor.adoptFd(prefMapFd) : null;
760         final ParcelFileDescriptor ipcPfd = ParcelFileDescriptor.adoptFd(ipcFd);
761         final ParcelFileDescriptor crashPfd =
762                 (crashFd >= 0) ? ParcelFileDescriptor.adoptFd(crashFd) : null;
763         final ParcelFileDescriptor crashAnnotationPfd =
764                 (crashAnnotationFd >= 0) ? ParcelFileDescriptor.adoptFd(crashAnnotationFd) : null;
765 
766         int started = IChildProcess.STARTED_FAIL;
767         RemoteException exception = null;
768         final String userSerialNumber = System.getenv("MOZ_ANDROID_USER_SERIAL_NUMBER");
769         final String crashHandler = GeckoAppShell.getCrashHandlerService() != null ?
770                 GeckoAppShell.getCrashHandlerService().getName() : null;
771         try {
772             started = child.start(this, mInstanceId, args, extras, flags, userSerialNumber, crashHandler,
773                     prefsPfd, prefMapPfd, ipcPfd, crashPfd, crashAnnotationPfd);
774         } catch (final RemoteException e) {
775             exception = e;
776         }
777 
778         if (crashAnnotationPfd != null) {
779             crashAnnotationPfd.detachFd();
780         }
781         if (crashPfd != null) {
782             crashPfd.detachFd();
783         }
784         ipcPfd.detachFd();
785         if (prefMapPfd != null) {
786             prefMapPfd.detachFd();
787         }
788         if (prefsPfd != null) {
789             prefsPfd.detachFd();
790         }
791 
792         if (started == IChildProcess.STARTED_OK) {
793             result.complete(connection.getPid());
794             return;
795         } else if (started == IChildProcess.STARTED_BUSY) {
796             // This process is owned by a different runtime, so we can't use
797             // it. Let's try another process.
798             // Note: this strategy is pretty bad, we go through each process in
799             // sequence until one works, the multiple runtime case is test-only
800             // for now, so that's ok. We can improve on this if we eventually
801             // end up needing something fancier.
802             Log.w(LOGTAG, "Trying a different process");
803             connection.unbind().accept(unused ->
804                 start(result, type, args, extras, flags, prefsFd, prefMapFd, ipcFd,
805                         crashFd, crashAnnotationFd, /* isRetry */ false));
806             return;
807         }
808 
809         // Whether retrying or not, we should always unbind connection so that it gets cleaned up.
810         final GeckoResult<Void> unbindResult = connection.unbind();
811 
812         // We always complete result exceptionally if the unbind fails
813         acceptUnbindFailure(unbindResult, result, exception, type, isRetry);
814 
815         if (isRetry) {
816             // If we've already retried, just assemble an error message and completeExceptionally.
817             Log.e(LOGTAG, "Cannot restart child " + type.toString());
818             final StringBuilder builder = new StringBuilder("Cannot restart child.");
819             if (prevException != null) {
820                 builder.append(" Initial RemoteException: ");
821                 builder.append(prevException.toString());
822             }
823             if (exception != null) {
824                 builder.append(" Second RemoteException: ");
825                 builder.append(exception.toString());
826             }
827             if (exception == null && prevException == null) {
828                 builder.append(" No exceptions thrown; type = ");
829                 builder.append(type.toString());
830             }
831 
832             final RuntimeException completionException = new RuntimeException(builder.toString());
833             unbindResult.accept(v -> {
834                 result.completeExceptionally(completionException);
835             });
836             return;
837         }
838 
839         // Attempt to retry the connection once we've finished unbinding.
840         Log.w(LOGTAG, "Attempting to kill running child " + type.toString());
841         final RemoteException captureException = exception;
842         unbindResult.accept(v -> {
843             start(result, type, args, extras, flags, prefsFd, prefMapFd, ipcFd,
844                   crashFd, crashAnnotationFd, /* isRetry */ true, captureException);
845         });
846     }
847 
848 } // GeckoProcessManager
849