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