Bug 1446161 - Asynchronously launch preallocated content processes using MozPromise. r=mccr8
authorJed Davis <jld@mozilla.com>
Wed, 28 Nov 2018 20:42:31 +0000
changeset 505022 9f5e62b653028ed2b31c16c2556343739a0f55b8
parent 505021 3cbd2cf23c867eddeaeccdfffc70485f2ad3c9c0
child 505023 41195aed7eef4e1575d37e806bc53876043ad376
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1446161
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1446161 - Asynchronously launch preallocated content processes using MozPromise. r=mccr8 There are several layers to this patch: 1. GeckoChildProcessHost now exposes a promise that's resolved when the process handle is available (or rejected if launch failed), as a nonblocking alternative to LaunchAndWaitForProcessHandle. 2. ContentParent builds on this with the private method LaunchSubprocessAsync and the public method PreallocateProcessAsync; synchronous launch continues to exist for the regular on-demand launch path, for the time being. 3. PreallocatedProcessManager now uses async launch, and handles the new "launch in progress" state appropriately. Depends on D8942 Differential Revision: https://phabricator.services.mozilla.com/D8943
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PreallocatedProcessManager.cpp
ipc/chromium/src/base/shared_memory.h
ipc/chromium/src/base/shared_memory_posix.cc
ipc/chromium/src/base/shared_memory_win.cc
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/GeckoChildProcessHost.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -617,30 +617,26 @@ static const char* sObserverTopics[] = {
 };
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
 bool ContentParent::sEarlySandboxInit = false;
 #endif
 
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
-/*static*/ already_AddRefed<ContentParent>
+/*static*/ RefPtr<ContentParent::LaunchPromise>
 ContentParent::PreallocateProcess()
 {
   RefPtr<ContentParent> process =
     new ContentParent(/* aOpener = */ nullptr,
                       NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
                       eNotRecordingOrReplaying,
                       /* aRecordingFile = */ EmptyString());
 
-  if (!process->LaunchSubprocess(PROCESS_PRIORITY_PREALLOC)) {
-    return nullptr;
-  }
-
-  return process.forget();
+  return process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC);
 }
 
 /*static*/ void
 ContentParent::StartUp()
 {
   // We could launch sub processes from content process
   // FIXME Bug 1023701 - Stop using ContentParent static methods in
   // child process
@@ -895,17 +891,17 @@ ContentParent::GetNewOrUsedBrowserProces
       p->mActivateTS = TimeStamp::Now();
       return p.forget();
     }
   }
 
   // Create a new process from scratch.
   RefPtr<ContentParent> p = new ContentParent(aOpener, aRemoteType, recordReplayState, recordingFile);
 
-  if (!p->LaunchSubprocess(aPriority)) {
+  if (!p->LaunchSubprocessSync(aPriority)) {
     return nullptr;
   }
 
   // Until the new process is ready let's not allow to start up any preallocated processes.
   PreallocatedProcessManager::AddBlocker(p);
 
   if (recordReplayState == eNotRecordingOrReplaying) {
     contentParents.AppendElement(p);
@@ -928,17 +924,17 @@ ContentParent::GetNewOrUsedJSPluginProce
   }
 
   if (p) {
     return p.forget();
   }
 
   p = new ContentParent(aPluginID);
 
-  if (!p->LaunchSubprocess(aPriority)) {
+  if (!p->LaunchSubprocessSync(aPriority)) {
     return nullptr;
   }
 
   sJSPluginContentParents->Put(aPluginID, p);
 
   return p.forget();
 }
 
@@ -2194,24 +2190,37 @@ ContentParent::AppendSandboxParams(std::
                sMacSandboxParams->begin(),
                sMacSandboxParams->end());
 
   // Append remaining arguments.
   AppendDynamicSandboxParams(aArgs);
 }
 #endif // XP_MACOSX && MOZ_CONTENT_SANDBOX
 
-bool
-ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
+void
+ContentParent::LaunchSubprocessInternal(
+  ProcessPriority aInitialPriority,
+  mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval)
 {
   AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess", OTHER);
+  const bool isSync = aRetval.is<bool*>();
+
+  auto earlyReject = [aRetval, isSync]() {
+    if (isSync) {
+      *aRetval.as<bool*>() = false;
+    } else {
+      *aRetval.as<RefPtr<LaunchPromise>*>() = LaunchPromise::CreateAndReject(
+        GeckoChildProcessHost::LaunchError(), __func__);
+    }
+  };
 
   if (!ContentProcessManager::GetSingleton()) {
     // Shutdown has begun, we shouldn't spawn any more child processes.
-    return false;
+    earlyReject();
+    return;
   }
 
   std::vector<std::string> extraArgs;
   extraArgs.push_back("-childID");
   char idStr[21];
   SprintfLiteral(idStr, "%" PRId64, static_cast<uint64_t>(mChildID));
   extraArgs.push_back(idStr);
   extraArgs.push_back(IsForBrowser() ? "-isForBrowser" : "-notForBrowser");
@@ -2226,22 +2235,24 @@ ContentParent::LaunchSubprocess(ProcessP
   nsAutoCStringN<1024> prefs;
   Preferences::SerializePreferences(prefs);
 
   // Set up the shared memory.
   base::SharedMemory shm;
   if (!shm.Create(prefs.Length())) {
     NS_ERROR("failed to create shared memory in the parent");
     MarkAsDead();
-    return false;
+    earlyReject();
+    return;
   }
   if (!shm.Map(prefs.Length())) {
     NS_ERROR("failed to map shared memory in the parent");
     MarkAsDead();
-    return false;
+    earlyReject();
+    return;
   }
 
   // Copy the serialized prefs into the shared memory.
   memcpy(static_cast<char*>(shm.memory()), prefs.get(), prefs.Length());
 
   // Formats a pointer or pointer-sized-integer as a string suitable for passing
   // in an arguments list.
   auto formatPtrArg = [] (auto arg) {
@@ -2307,54 +2318,110 @@ ContentParent::LaunchSubprocess(ProcessP
                               : (int) recordreplay::ProcessKind::MiddlemanReplaying);
     extraArgs.push_back(recordreplay::gProcessKindOption);
     extraArgs.push_back(buf.get());
 
     extraArgs.push_back(recordreplay::gRecordingFileOption);
     extraArgs.push_back(NS_ConvertUTF16toUTF8(mRecordingFile).get());
   }
 
-  if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
+  RefPtr<ContentParent> self(this);
+
+  auto reject = [self, this](GeckoChildProcessHost::LaunchError err) {
     NS_ERROR("failed to launch child in the parent");
     MarkAsDead();
-    return false;
-  }
+    return LaunchPromise::CreateAndReject(err, __func__);
+  };
 
   // See also ActorDestroy.
   mSelfRef = this;
 
-  base::ProcessId procId =
-    base::GetProcId(mSubprocess->GetChildProcessHandle());
-  Open(mSubprocess->GetChannel(), procId);
+  // Lifetime note: the GeckoChildProcessHost holds a strong reference
+  // to the launch promise, which takes ownership of these closures,
+  // which hold strong references to this ContentParent; the
+  // ContentParent then owns the GeckoChildProcessHost (and that
+  // ownership is not exposed to the cycle collector).  Therefore,
+  // this all stays alive until the promise is resolved or rejected.
+
+  auto resolve = [self, this, aInitialPriority, isSync,
+                  // Transfer ownership of RAII file descriptor/handle
+                  // holders so that they won't be closed before the
+                  // child can inherit them.
+                  shm = std::move(shm),
+                  prefMapHandle = std::move(prefMapHandle)
+                 ](base::ProcessHandle handle) {
+    AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess::resolve", OTHER);
+
+    base::ProcessId procId = base::GetProcId(handle);
+    Open(mSubprocess->GetChannel(), procId);
 #ifdef MOZ_CODE_COVERAGE
-  Unused << SendShareCodeCoverageMutex(
-              CodeCoverageHandler::Get()->GetMutexHandle(procId));
+    Unused << SendShareCodeCoverageMutex(
+      CodeCoverageHandler::Get()->GetMutexHandle(procId));
 #endif
 
-  InitInternal(aInitialPriority);
-
-  ContentProcessManager::GetSingleton()->AddContentProcess(this);
-
-  mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
-
-  // Set a reply timeout for CPOWs.
-  SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    nsAutoString cpId;
-    cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
-    obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
-  }
-
-  Init();
-
-  // Launch time telemetry will return in a later patch (bug 1474991).
-
-  return true;
+    mIsAlive = true;
+    InitInternal(aInitialPriority);
+
+    ContentProcessManager::GetSingleton()->AddContentProcess(this);
+
+    mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
+
+    // Set a reply timeout for CPOWs.
+    SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      nsAutoString cpId;
+      cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
+      obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
+    }
+
+    Init();
+
+    // Launch time telemetry will return in a later patch (bug 1474991).
+    Unused << isSync;
+
+    return LaunchPromise::CreateAndResolve(self, __func__);
+  };
+
+  if (isSync) {
+    bool ok = mSubprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs));
+    if (ok) {
+      Unused << resolve(mSubprocess->GetChildProcessHandle());
+    } else {
+      Unused << reject(GeckoChildProcessHost::LaunchError{});
+    }
+    *aRetval.as<bool*>() = ok;
+  } else {
+    auto* retptr = aRetval.as<RefPtr<LaunchPromise>*>();
+    if (mSubprocess->AsyncLaunch(std::move(extraArgs))) {
+      RefPtr<GeckoChildProcessHost::HandlePromise> ready =
+        mSubprocess->WhenProcessHandleReady();
+      *retptr = ready->Then(GetCurrentThreadSerialEventTarget(), __func__,
+                            std::move(resolve), std::move(reject));
+    } else {
+      *retptr = reject(GeckoChildProcessHost::LaunchError{});
+    }
+  }
+}
+
+/* static */ bool
+ContentParent::LaunchSubprocessSync(hal::ProcessPriority aInitialPriority)
+{
+  bool retval;
+  LaunchSubprocessInternal(aInitialPriority, mozilla::AsVariant(&retval));
+  return retval;
+}
+
+/* static */ RefPtr<ContentParent::LaunchPromise>
+ContentParent::LaunchSubprocessAsync(hal::ProcessPriority aInitialPriority)
+{
+  RefPtr<LaunchPromise> retval;
+  LaunchSubprocessInternal(aInitialPriority, mozilla::AsVariant(&retval));
+  return retval;
 }
 
 ContentParent::ContentParent(ContentParent* aOpener,
                              const nsAString& aRemoteType,
                              RecordReplayState aRecordReplayState,
                              const nsAString& aRecordingFile,
                              int32_t aJSPluginID)
   : nsIContentParent()
@@ -2365,17 +2432,17 @@ ContentParent::ContentParent(ContentPare
   , mOpener(aOpener)
   , mRemoteType(aRemoteType)
   , mChildID(gContentChildID++)
   , mGeolocationWatchID(-1)
   , mJSPluginID(aJSPluginID)
   , mRemoteWorkerActors(0)
   , mNumDestroyingTabs(0)
   , mIsAvailable(true)
-  , mIsAlive(true)
+  , mIsAlive(false)
   , mIsForBrowser(!mRemoteType.IsEmpty())
   , mRecordReplayState(aRecordReplayState)
   , mRecordingFile(aRecordingFile)
   , mCalledClose(false)
   , mCalledKillHard(false)
   , mCreatedPairedMinidumps(false)
   , mShutdownPending(false)
   , mIPCOpen(true)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -14,16 +14,17 @@
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReportingProcess.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/Variant.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsDataHashtable.h"
 #include "nsPluginTags.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashKeys.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
@@ -126,20 +127,23 @@ class ContentParent final : public PCont
 #ifdef FUZZING
   friend class mozilla::ipc::ProtocolFuzzerHelper;
 #endif
 
 public:
 
   virtual bool IsContentParent() const override { return true; }
 
+  using LaunchError = GeckoChildProcessHost::LaunchError;
+  using LaunchPromise = GeckoChildProcessHost::LaunchPromise<RefPtr<ContentParent>>;
+
   /**
    * Create a subprocess suitable for use later as a content process.
    */
-  static already_AddRefed<ContentParent> PreallocateProcess();
+  static RefPtr<LaunchPromise> PreallocateProcess();
 
   /**
    * Start up the content-process machinery.  This might include
    * scheduling pre-launch tasks.
    */
   static void StartUp();
 
   /** Shut down the content-process machinery. */
@@ -793,17 +797,30 @@ private:
   ContentParent(ContentParent* aOpener,
                 const nsAString& aRemoteType,
                 RecordReplayState aRecordReplayState,
                 const nsAString& aRecordingFile,
                 int32_t aPluginID);
 
   // Launch the subprocess and associated initialization.
   // Returns false if the process fails to start.
-  bool LaunchSubprocess(hal::ProcessPriority aInitialPriority = hal::PROCESS_PRIORITY_FOREGROUND);
+  // Deprecated in favor of LaunchSubprocessAsync.
+  bool LaunchSubprocessSync(hal::ProcessPriority aInitialPriority);
+
+  // Launch the subprocess and associated initialization;
+  // returns a promise and signals failure by rejecting.
+  // OS-level launching work is dispatched to another thread, but some
+  // initialization (creating IPDL actors, etc.; see Init()) is run on
+  // the main thread.
+  RefPtr<LaunchPromise> LaunchSubprocessAsync(hal::ProcessPriority aInitialPriority);
+
+  // Common implementation of LaunchSubprocess{Sync,Async}.
+  void LaunchSubprocessInternal(
+    hal::ProcessPriority aInitialPriority,
+    mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval);
 
   // Common initialization after sub process launch.
   void InitInternal(ProcessPriority aPriority);
 
   // Generate a minidump for the child process and one for the main process
   void GeneratePairedMinidump(const char* aReason);
 
   virtual ~ContentParent();
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -43,17 +43,17 @@ public:
   void RemoveBlocker(ContentParent* aParent);
   already_AddRefed<ContentParent> Take();
   bool Provide(ContentParent* aParent);
 
 private:
   static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
 
   PreallocatedProcessManagerImpl();
-  ~PreallocatedProcessManagerImpl() {}
+  ~PreallocatedProcessManagerImpl();
   DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
 
   void Init();
 
   bool CanAllocate();
   void AllocateAfterDelay();
   void AllocateOnIdle();
   void AllocateNow();
@@ -62,18 +62,23 @@ private:
   void Enable();
   void Disable();
   void CloseProcess();
 
   void ObserveProcessShutdown(nsISupports* aSubject);
 
   bool mEnabled;
   bool mShutdown;
+  bool mLaunchInProgress;
   RefPtr<ContentParent> mPreallocatedProcess;
   nsTHashtable<nsUint64HashKey> mBlockers;
+
+  bool IsEmpty() const {
+    return !mPreallocatedProcess && !mLaunchInProgress;
+  }
 };
 
 /* static */ StaticRefPtr<PreallocatedProcessManagerImpl>
 PreallocatedProcessManagerImpl::sSingleton;
 
 /* static */ PreallocatedProcessManagerImpl*
 PreallocatedProcessManagerImpl::Singleton()
 {
@@ -87,18 +92,26 @@ PreallocatedProcessManagerImpl::Singleto
   return sSingleton;
 }
 
 NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver)
 
 PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
   : mEnabled(false)
   , mShutdown(false)
+  , mLaunchInProgress(false)
 {}
 
+PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl()
+{
+  // This shouldn't happen, because the promise callbacks should
+  // hold strong references, but let't make absolutely sure:
+  MOZ_RELEASE_ASSERT(!mLaunchInProgress);
+}
+
 void
 PreallocatedProcessManagerImpl::Init()
 {
   Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
   // We have to respect processCount at all time. This is especially important
   // for testing.
   Preferences::AddStrongObserver(this, "dom.ipc.processCount");
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
@@ -174,16 +187,19 @@ PreallocatedProcessManagerImpl::Take()
   }
 
   return mPreallocatedProcess.forget();
 }
 
 bool
 PreallocatedProcessManagerImpl::Provide(ContentParent* aParent)
 {
+  // This will take the already-running process even if there's a
+  // launch in progress; if that process hasn't been taken by the
+  // time the launch completes, the new process will be shut down.
   if (mEnabled && !mShutdown && !mPreallocatedProcess) {
     mPreallocatedProcess = aParent;
   }
 
   // We might get a call from both NotifyTabDestroying and NotifyTabDestroyed with the same
   // ContentParent. Returning true here for both calls is important to avoid the cached process
   // to be destroyed.
   return aParent == mPreallocatedProcess;
@@ -215,27 +231,27 @@ PreallocatedProcessManagerImpl::RemoveBl
   // This used to assert that the blocker existed, but preallocated
   // processes aren't blockers anymore because it's not useful and
   // interferes with async launch, and it's simpler if content
   // processes don't need to remember whether they were preallocated.
   // (And preallocated processes can't AddBlocker when taken, because
   // it's possible for a short-lived process to be recycled through
   // Provide() and Take() before reaching RecvFirstIdle.)
   mBlockers.RemoveEntry(childID);
-  if (!mPreallocatedProcess && mBlockers.IsEmpty()) {
+  if (IsEmpty() && mBlockers.IsEmpty()) {
     AllocateAfterDelay();
   }
 }
 
 bool
 PreallocatedProcessManagerImpl::CanAllocate()
 {
   return mEnabled &&
          mBlockers.IsEmpty() &&
-         !mPreallocatedProcess &&
+         IsEmpty() &&
          !mShutdown &&
          !ContentParent::IsMaxProcessCountReached(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
 }
 
 void
 PreallocatedProcessManagerImpl::AllocateAfterDelay()
 {
   if (!mEnabled) {
@@ -262,24 +278,41 @@ PreallocatedProcessManagerImpl::Allocate
                       this,
                       &PreallocatedProcessManagerImpl::AllocateNow));
 }
 
 void
 PreallocatedProcessManagerImpl::AllocateNow()
 {
   if (!CanAllocate()) {
-    if (mEnabled && !mShutdown && !mPreallocatedProcess && !mBlockers.IsEmpty()) {
+    if (mEnabled && !mShutdown && IsEmpty() && !mBlockers.IsEmpty()) {
       // If it's too early to allocate a process let's retry later.
       AllocateAfterDelay();
     }
     return;
   }
 
-  mPreallocatedProcess = ContentParent::PreallocateProcess();
+  RefPtr<PreallocatedProcessManagerImpl> self(this);
+  mLaunchInProgress = true;
+
+  ContentParent::PreallocateProcess()
+    ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+
+           [self, this](const RefPtr<ContentParent>& process) {
+             mLaunchInProgress = false;
+             if (CanAllocate()) {
+               mPreallocatedProcess = process;
+             } else {
+               process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
+             }
+           },
+
+           [self, this](ContentParent::LaunchError err) {
+             mLaunchInProgress = false;
+           });
 }
 
 void
 PreallocatedProcessManagerImpl::Disable()
 {
   if (!mEnabled) {
     return;
   }
--- a/ipc/chromium/src/base/shared_memory.h
+++ b/ipc/chromium/src/base/shared_memory.h
@@ -38,16 +38,19 @@ class SharedMemory {
 
   // Create a new SharedMemory object from an existing, open
   // shared memory file.
   SharedMemory(SharedMemoryHandle init_handle, bool read_only)
     : SharedMemory() {
     SetHandle(init_handle, read_only);
   }
 
+  // Move constructor; transfers ownership.
+  SharedMemory(SharedMemory&& other);
+
   // Destructor.  Will close any open files.
   ~SharedMemory();
 
   // Initialize a new SharedMemory object from an existing, open
   // shared memory file.
   bool SetHandle(SharedMemoryHandle handle, bool read_only);
 
   // Return true iff the given handle is valid (i.e. not the distingished
--- a/ipc/chromium/src/base/shared_memory_posix.cc
+++ b/ipc/chromium/src/base/shared_memory_posix.cc
@@ -26,16 +26,30 @@ namespace base {
 
 SharedMemory::SharedMemory()
     : mapped_file_(-1),
       memory_(NULL),
       read_only_(false),
       max_size_(0) {
 }
 
+SharedMemory::SharedMemory(SharedMemory&& other) {
+  if (this == &other) {
+    return;
+  }
+
+  mapped_file_ = other.mapped_file_;
+  memory_ = other.memory_;
+  read_only_ = other.read_only_;
+  max_size_ = other.max_size_;
+
+  other.mapped_file_ = -1;
+  other.memory_ = nullptr;
+}
+
 SharedMemory::~SharedMemory() {
   Close();
 }
 
 bool SharedMemory::SetHandle(SharedMemoryHandle handle, bool read_only) {
   DCHECK(mapped_file_ == -1);
 
   mapped_file_ = handle.fd;
--- a/ipc/chromium/src/base/shared_memory_win.cc
+++ b/ipc/chromium/src/base/shared_memory_win.cc
@@ -59,16 +59,31 @@ namespace base {
 SharedMemory::SharedMemory()
     : external_section_(false),
       mapped_file_(NULL),
       memory_(NULL),
       read_only_(false),
       max_size_(0) {
 }
 
+SharedMemory::SharedMemory(SharedMemory&& other) {
+  if (this == &other) {
+    return;
+  }
+
+  mapped_file_ = other.mapped_file_;
+  memory_ = other.memory_;
+  read_only_ = other.read_only_;
+  max_size_ = other.max_size_;
+  external_section_ = other.external_section_;
+
+  other.mapped_file_ = nullptr;
+  other.memory_ = nullptr;
+}
+
 SharedMemory::~SharedMemory() {
   external_section_ = true;
   Close();
 }
 
 bool SharedMemory::SetHandle(SharedMemoryHandle handle, bool read_only) {
   DCHECK(mapped_file_ == NULL);
 
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -341,16 +341,19 @@ GeckoChildProcessHost::SyncLaunch(std::v
 
 bool
 GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts)
 {
   PrepareLaunch();
 
   MessageLoop* ioLoop = XRE_GetIOMessageLoop();
 
+  MOZ_ASSERT(mHandlePromise == nullptr);
+  mHandlePromise = new HandlePromise::Private(__func__);
+
   // Currently this can't fail (see the MOZ_ALWAYS_SUCCEEDS in
   // MessageLoop::PostTask_Helper), but in the future it possibly
   // could, in which case this method could return false.
   ioLoop->PostTask(NewNonOwningRunnableMethod<std::vector<std::string>>(
     "ipc::GeckoChildProcessHost::RunPerformAsyncLaunch",
     this,
     &GeckoChildProcessHost::RunPerformAsyncLaunch,
     aExtraOpts));
@@ -489,16 +492,17 @@ GeckoChildProcessHost::RunPerformAsyncLa
   InitializeChannel();
 
   bool ok = PerformAsyncLaunch(aExtraOpts);
   if (!ok) {
     // WaitUntilConnected might be waiting for us to signal.
     // If something failed let's set the error state and notify.
     MonitorAutoLock lock(mMonitor);
     mProcessState = PROCESS_ERROR;
+    mHandlePromise->Reject(LaunchError{}, __func__);
     lock.Notify();
     CHROMIUM_LOG(ERROR) << "Failed to launch " <<
       XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
     Telemetry::Accumulate(Telemetry::SUBPROCESS_LAUNCH_FAILURE,
       nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
   }
   return ok;
 }
@@ -1110,16 +1114,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
 #else
   CrashReporter::RegisterChildCrashAnnotationFileDescriptor(process,
                                                             crashAnnotationReadPipe);
 #endif
   PR_Close(crashAnnotationWritePipe);
 
   MonitorAutoLock lock(mMonitor);
   mProcessState = PROCESS_CREATED;
+  mHandlePromise->Resolve(process, __func__);
   lock.Notify();
 
   mLaunchOptions = nullptr;
   return true;
 }
 
 bool
 GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid)
@@ -1134,16 +1139,17 @@ GeckoChildProcessHost::OpenPrivilegedHan
 
 void
 GeckoChildProcessHost::OnChannelConnected(int32_t peer_pid)
 {
   if (!OpenPrivilegedHandle(peer_pid)) {
     MOZ_CRASH("can't open handle to child process");
   }
   MonitorAutoLock lock(mMonitor);
+  MOZ_DIAGNOSTIC_ASSERT(mProcessState == PROCESS_CREATED);
   mProcessState = PROCESS_CONNECTED;
   lock.Notify();
 }
 
 void
 GeckoChildProcessHost::OnMessageReceived(IPC::Message&& aMsg)
 {
   // We never process messages ourself, just save them up for the next
@@ -1155,22 +1161,30 @@ void
 GeckoChildProcessHost::OnChannelError()
 {
   // Update the process state to an error state if we have a channel
   // error before we're connected. This fixes certain failures,
   // but does not address the full range of possible issues described
   // in the FIXME comment below.
   MonitorAutoLock lock(mMonitor);
   if (mProcessState < PROCESS_CONNECTED) {
+    MOZ_DIAGNOSTIC_ASSERT(mProcessState == PROCESS_CREATED);
     mProcessState = PROCESS_ERROR;
     lock.Notify();
   }
   // FIXME/bug 773925: save up this error for the next listener.
 }
 
+RefPtr<GeckoChildProcessHost::HandlePromise>
+GeckoChildProcessHost::WhenProcessHandleReady()
+{
+  MOZ_ASSERT(mHandlePromise != nullptr);
+  return mHandlePromise;
+}
+
 void
 GeckoChildProcessHost::GetQueuedMessages(std::queue<IPC::Message>& queue)
 {
   // If this is called off the IO thread, bad things will happen.
   DCHECK(MessageLoopForIO::current());
   swap(queue, mQueue);
   // We expect the next listener to take over processing of our queue.
 }
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -10,16 +10,17 @@
 #include "base/file_path.h"
 #include "base/process_util.h"
 #include "base/waitable_event.h"
 #include "chrome/common/child_process_host.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ipc/FileDescriptor.h"
 #include "mozilla/Monitor.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsCOMPtr.h"
 #include "nsXULAppAPI.h"        // for GeckoProcessType
 #include "nsString.h"
 
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
@@ -42,17 +43,18 @@ public:
                                  bool aIsFileContent = false);
 
   ~GeckoChildProcessHost();
 
   static uint32_t GetUniqueID();
 
   // Does not block.  The IPC channel may not be initialized yet, and
   // the child process may or may not have been created when this
-  // method returns.
+  // method returns.  This GeckoChildProcessHost must not be destroyed
+  // while the launch is in progress.
   bool AsyncLaunch(StringVector aExtraOpts=StringVector());
 
   virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0);
 
   // Block until the IPC channel for our subprocess is initialized and
   // the OS process is created.  The subprocess may or may not have
   // connected back to us when this method returns.
   //
@@ -72,16 +74,25 @@ public:
   bool SyncLaunch(StringVector aExtraOpts=StringVector(),
                   int32_t timeoutMs=0);
 
   virtual void OnChannelConnected(int32_t peer_pid) override;
   virtual void OnMessageReceived(IPC::Message&& aMsg) override;
   virtual void OnChannelError() override;
   virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override;
 
+  struct LaunchError {};
+  template <typename T>
+  using LaunchPromise = mozilla::MozPromise<T, LaunchError, /* excl: */ false>;
+  using HandlePromise = LaunchPromise<base::ProcessHandle>;
+
+  // Resolves to the process handle when it's available (see
+  // LaunchAndWaitForProcessHandle); use with AsyncLaunch.
+  RefPtr<HandlePromise> WhenProcessHandleReady();
+
   virtual void InitializeChannel();
 
   virtual bool CanShutdown() override { return true; }
 
   IPC::Channel* GetChannel() {
     return channelp();
   }
 
@@ -165,16 +176,17 @@ protected:
   int32_t mSandboxLevel;
 #endif
 #endif // XP_WIN
 
   ProcessHandle mChildProcessHandle;
 #if defined(OS_MACOSX)
   task_t mChildTask;
 #endif
+  RefPtr<HandlePromise::Private> mHandlePromise;
 
   bool OpenPrivilegedHandle(base::ProcessId aPid);
 
 private:
   DISALLOW_EVIL_CONSTRUCTORS(GeckoChildProcessHost);
 
   // Does the actual work for AsyncLaunch, on the IO thread.
   // (TODO, bug 1487287: move this to its own thread(s).)