Bug 1355634 - Use MozPromise to make ProfileGatherer more generic. draft
authorMarkus Stange <mstange@themasta.com>
Wed, 12 Apr 2017 16:57:58 -0400
changeset 561552 8d547ac8f15583899a11ef925f069350d67a5ff6
parent 561548 224cf214efae67b6da4c722005ef75ba67aecbd1
child 561684 0084a690e281655ab19c70fae7cb671e78f82a7d
push id53774
push userbmo:mstange@themasta.com
push dateWed, 12 Apr 2017 21:03:29 +0000
bugs1355634
milestone55.0a1
Bug 1355634 - Use MozPromise to make ProfileGatherer more generic. MozReview-Commit-ID: GKqxJW8zjca
tools/profiler/gecko/ProfileGatherer.cpp
tools/profiler/gecko/ProfileGatherer.h
tools/profiler/gecko/nsProfiler.cpp
tools/profiler/gecko/nsProfiler.h
tools/profiler/moz.build
--- a/tools/profiler/gecko/ProfileGatherer.cpp
+++ b/tools/profiler/gecko/ProfileGatherer.cpp
@@ -3,21 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ProfileGatherer.h"
 
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
-#include "nsLocalFile.h"
-#include "nsIFileStreams.h"
-
-using mozilla::dom::AutoJSAPI;
-using mozilla::dom::Promise;
 
 namespace mozilla {
 
 /**
  * When a subprocess exits before we've gathered profiles, we'll
  * store profiles for those processes until gathering starts. We'll
  * only store up to MAX_SUBPROCESS_EXIT_PROFILES. The buffer is
  * circular, so as soon as we receive another exit profile, we'll
@@ -45,22 +40,16 @@ ProfileGatherer::GatheredOOPProfile(cons
 
   if (!mGathering) {
     // If we're not actively gathering, then we don't actually
     // care that we gathered a profile here. This can happen for
     // processes that exit while profiling.
     return;
   }
 
-  if (NS_WARN_IF(!mPromise && !mFile)) {
-    // If we're not holding on to a Promise, then someone is
-    // calling us erroneously.
-    return;
-  }
-
   MOZ_RELEASE_ASSERT(mWriter.isSome(), "Should always have a writer if mGathering is true");
 
   mWriter->Splice(PromiseFlatCString(aProfile).get());
 
   mPendingProfiles--;
 
   if (mPendingProfiles == 0) {
     // We've got all of the async profiles now. Let's
@@ -70,184 +59,116 @@ ProfileGatherer::GatheredOOPProfile(cons
 }
 
 void
 ProfileGatherer::WillGatherOOPProfile()
 {
   mPendingProfiles++;
 }
 
-void
-ProfileGatherer::Start(double aSinceTime, Promise* aPromise)
+RefPtr<ProfileGatherer::ProfileGatherPromise>
+ProfileGatherer::Start(double aSinceTime)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   if (mGathering) {
-    // If we're already gathering, reject the promise - this isn't going
-    // to end well.
-    if (aPromise) {
-      aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
-    }
-    return;
+    // If we're already gathering, return a rejected promise - this isn't
+    // going to end well.
+    return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
   }
 
-  mPromise = aPromise;
-
-  Start2(aSinceTime);
-}
-
-void
-ProfileGatherer::Start(double aSinceTime, const nsACString& aFileName)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
-  nsresult rv = file->InitWithNativePath(aFileName);
-  if (NS_FAILED(rv)) {
-    MOZ_CRASH();
-  }
-
-  if (mGathering) {
-    return;
-  }
-
-  mFile = file;
-
-  Start2(aSinceTime);
-}
-
-// This is the common tail shared by both Start() methods.
-void
-ProfileGatherer::Start2(double aSinceTime)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
   mGathering = true;
   mPendingProfiles = 0;
-  mWriter.emplace();
 
   // Send a notification to request profiles from other processes. The
   // observers of this notification will call WillGatherOOPProfile() which
   // increments mPendingProfiles.
   // Do this before the call to profiler_stream_json_for_this_process because
   // that call is slow and we want to let the other processes grab their
   // profiles as soon as possible.
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os) {
     DebugOnly<nsresult> rv =
       os->NotifyObservers(this, "profiler-subprocess-gather", nullptr);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NotifyObservers failed");
   }
 
+  mWriter.emplace();
+
   // Start building up the JSON result and grab the profile from this process.
   mWriter->Start(SpliceableJSONWriter::SingleLineStyle);
   if (!profiler_stream_json_for_this_process(*mWriter, aSinceTime)) {
     // The profiler is inactive. This either means that it was inactive even
     // at the time that ProfileGatherer::Start() was called, or that it was
     // stopped on a different thread since that call. Either way, we need to
     // reject the promise and stop gathering.
-    Cancel();
-    return;
+    return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
   }
 
   mWriter->StartArrayProperty("processes");
 
   // If we have any process exit profiles, add them immediately, and clear
   // mExitProfiles.
   for (size_t i = 0; i < mExitProfiles.Length(); ++i) {
     if (!mExitProfiles[i].IsEmpty()) {
       mWriter->Splice(mExitProfiles[i].get());
     }
   }
   mExitProfiles.Clear();
 
+  mPromiseHolder.emplace();
+  RefPtr<ProfileGatherPromise> promise = mPromiseHolder->Ensure(__func__);
+
   // Keep the array property "processes" and the root object in mWriter open
   // until Finish() is called. As profiles from the other processes come in,
   // they will be inserted and end up in the right spot. Finish() will close
   // the array and the root object.
 
   if (!mPendingProfiles) {
     Finish();
   }
+
+  return promise;
 }
 
 void
 ProfileGatherer::Finish()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(mWriter.isSome());
+  MOZ_RELEASE_ASSERT(mPromiseHolder.isSome());
 
   // Close the "processes" array property.
   mWriter->EndArray();
 
   // Close the root object of the generated JSON.
   mWriter->End();
 
   UniquePtr<char[]> buf = mWriter->WriteFunc()->CopyData();
-
-  if (mFile) {
-    nsCOMPtr<nsIFileOutputStream> of =
-      do_CreateInstance("@mozilla.org/network/file-output-stream;1");
-    of->Init(mFile, -1, -1, 0);
-    uint32_t sz;
-    of->Write(buf.get(), strlen(buf.get()), &sz);
-    of->Close();
-    Reset();
-    return;
-  }
-
-  AutoJSAPI jsapi;
-  if (NS_WARN_IF(!jsapi.Init(mPromise->GlobalJSObject()))) {
-    // We're really hosed if we can't get a JS context for some reason.
-    Reset();
-    return;
-  }
-
-  JSContext* cx = jsapi.cx();
-
-  // Now parse the JSON so that we resolve with a JS Object.
-  JS::RootedValue val(cx);
-  {
-    NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
-    if (!JS_ParseJSON(cx, static_cast<const char16_t*>(js_string.get()),
-                      js_string.Length(), &val)) {
-      if (!jsapi.HasException()) {
-        mPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
-      } else {
-        JS::RootedValue exn(cx);
-        DebugOnly<bool> gotException = jsapi.StealException(&exn);
-        MOZ_ASSERT(gotException);
-
-        jsapi.ClearException();
-        mPromise->MaybeReject(cx, exn);
-      }
-    } else {
-      mPromise->MaybeResolve(val);
-    }
-  }
+  nsCString result(buf.get());
+  mPromiseHolder->Resolve(result, __func__);
 
   Reset();
 }
 
 void
 ProfileGatherer::Reset()
 {
-  mPromise = nullptr;
-  mFile = nullptr;
+  mPromiseHolder.reset();
   mPendingProfiles = 0;
   mGathering = false;
   mWriter.reset();
 }
 
 void
 ProfileGatherer::Cancel()
 {
   // If we have a Promise in flight, we should reject it.
-  if (mPromise) {
-    mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  if (mPromiseHolder.isSome()) {
+    mPromiseHolder->RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
   }
   Reset();
 }
 
 void
 ProfileGatherer::OOPExitProfile(const nsACString& aProfile)
 {
   // Append the exit profile to mExitProfiles so that it can be picked up the
--- a/tools/profiler/gecko/ProfileGatherer.h
+++ b/tools/profiler/gecko/ProfileGatherer.h
@@ -1,44 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZ_PROFILE_GATHERER_H
 #define MOZ_PROFILE_GATHERER_H
 
-#include "mozilla/dom/Promise.h"
 #include "nsIFile.h"
 #include "ProfileJSONWriter.h"
+#include "mozilla/MozPromise.h"
 
 namespace mozilla {
 
 // This class holds the state for an async profile-gathering request.
 class ProfileGatherer final : public nsISupports
 {
 public:
   NS_DECL_ISUPPORTS
 
+  typedef MozPromise<nsCString, nsresult, false> ProfileGatherPromise;
+
   explicit ProfileGatherer();
   void WillGatherOOPProfile();
   void GatheredOOPProfile(const nsACString& aProfile);
-  void Start(double aSinceTime, mozilla::dom::Promise* aPromise);
-  void Start(double aSinceTime, const nsACString& aFileName);
-  void Cancel();
+  RefPtr<ProfileGatherPromise> Start(double aSinceTime);
   void OOPExitProfile(const nsACString& aProfile);
 
 private:
   ~ProfileGatherer();
+  void Cancel();
   void Finish();
   void Reset();
-  void Start2(double aSinceTime);
 
   nsTArray<nsCString> mExitProfiles;
-  RefPtr<mozilla::dom::Promise> mPromise;
-  nsCOMPtr<nsIFile> mFile;
+  Maybe<MozPromiseHolder<ProfileGatherPromise>> mPromiseHolder;
   Maybe<SpliceableChunkedJSONWriter> mWriter;
   uint32_t mPendingProfiles;
   bool mGathering;
 };
 
 } // namespace mozilla
 
 #endif
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -2,34 +2,39 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <string>
 #include <sstream>
 #include "GeckoProfiler.h"
+#include "nsIFileStreams.h"
 #include "nsProfiler.h"
 #include "nsProfilerStartParams.h"
 #include "nsMemory.h"
 #include "nsString.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILoadContext.h"
 #include "nsIWebNavigation.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "shared-libraries.h"
 #include "js/Value.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Promise.h"
 #include "ProfileGatherer.h"
+#include "nsLocalFile.h"
+#include "platform.h"
 
-using mozilla::ErrorResult;
-using mozilla::dom::Promise;
+using namespace mozilla;
+
+using dom::AutoJSAPI;
+using dom::Promise;
 using std::string;
 
 NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler)
 
 nsProfiler::nsProfiler()
   : mLockedForPrivateBrowsing(false)
 {
 }
@@ -236,33 +241,85 @@ nsProfiler::GetProfileDataAsync(double a
   }
 
   ErrorResult result;
   RefPtr<Promise> promise = Promise::Create(go, result);
   if (NS_WARN_IF(result.Failed())) {
     return result.StealNSResult();
   }
 
-  mGatherer->Start(aSinceTime, promise);
+  mGatherer->Start(aSinceTime)->Then(
+    AbstractThread::MainThread(), __func__,
+    [promise](nsCString aResult) {
+      AutoJSAPI jsapi;
+      if (NS_WARN_IF(!jsapi.Init(promise->GlobalJSObject()))) {
+        // We're really hosed if we can't get a JS context for some reason.
+        promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
+        return;
+      }
+
+      JSContext* cx = jsapi.cx();
+
+      // Now parse the JSON so that we resolve with a JS Object.
+      JS::RootedValue val(cx);
+      {
+        NS_ConvertUTF8toUTF16 js_string(aResult);
+        if (!JS_ParseJSON(cx, static_cast<const char16_t*>(js_string.get()),
+                          js_string.Length(), &val)) {
+          if (!jsapi.HasException()) {
+            promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
+          } else {
+            JS::RootedValue exn(cx);
+            DebugOnly<bool> gotException = jsapi.StealException(&exn);
+            MOZ_ASSERT(gotException);
+
+            jsapi.ClearException();
+            promise->MaybeReject(cx, exn);
+          }
+        } else {
+          promise->MaybeResolve(val);
+        }
+      }
+    },
+    [promise](nsresult aRv) {
+      promise->MaybeReject(aRv);
+    });
 
   promise.forget(aPromise);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsProfiler::DumpProfileToFileAsync(const nsACString& aFilename,
                                    double aSinceTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mGatherer) {
     return NS_ERROR_FAILURE;
   }
 
-  mGatherer->Start(aSinceTime, aFilename);
+  nsCString filename(aFilename);
+
+  mGatherer->Start(aSinceTime)->Then(
+    AbstractThread::MainThread(), __func__,
+    [filename](const nsCString& aResult) {
+      nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+      nsresult rv = file->InitWithNativePath(filename);
+      if (NS_FAILED(rv)) {
+        MOZ_CRASH();
+      }
+      nsCOMPtr<nsIFileOutputStream> of =
+        do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+      of->Init(file, -1, -1, 0);
+      uint32_t sz;
+      of->Write(aResult.get(), aResult.Length(), &sz);
+      of->Close();
+    },
+    [](nsresult aRv) { });
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsProfiler::GetElapsedTime(double* aElapsedTime)
 {
--- a/tools/profiler/gecko/nsProfiler.h
+++ b/tools/profiler/gecko/nsProfiler.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _NSPROFILER_H_
 #define _NSPROFILER_H_
 
 #include "nsIProfiler.h"
 #include "nsIObserver.h"
 #include "mozilla/Attributes.h"
+#include "nsServiceManagerUtils.h"
 
 namespace mozilla {
 class ProfileGatherer;
 }
 
 class nsProfiler final : public nsIProfiler, public nsIObserver
 {
 public:
@@ -34,14 +35,14 @@ public:
 
     void WillGatherOOPProfile();
     void GatheredOOPProfile(const nsACString& aProfile);
     void OOPExitProfile(const nsACString& aProfile);
 
 private:
     ~nsProfiler();
 
-    RefPtr<ProfileGatherer> mGatherer;
+    RefPtr<mozilla::ProfileGatherer> mGatherer;
     bool mLockedForPrivateBrowsing;
 };
 
 #endif /* _NSPROFILER_H_ */
 
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -23,29 +23,33 @@ if CONFIG['MOZ_GECKO_PROFILER']:
         'core/ProfileBuffer.cpp',
         'core/ProfileBufferEntry.cpp',
         'core/ProfileJSONWriter.cpp',
         'core/ProfilerBacktrace.cpp',
         'core/ProfilerMarkers.cpp',
         'core/StackTop.cpp',
         'core/ThreadInfo.cpp',
         'gecko/CrossProcessProfilerController.cpp',
-        'gecko/nsProfiler.cpp',
         'gecko/nsProfilerFactory.cpp',
         'gecko/nsProfilerStartParams.cpp',
+        'gecko/ProfileGatherer.cpp',
         'gecko/ProfilerIOInterposeObserver.cpp',
         'gecko/ThreadResponsiveness.cpp',
     ]
     if CONFIG['OS_TARGET'] == 'Darwin':
+        # This file cannot be built in unified mode because it includes
+        # "nsLocalFile.h", which pulls in a system header which uses a type
+        # called TextRange, which conflicts with mozilla::TextRange due to
+        # a "using namespace mozilla;" declaration from a different file.
         SOURCES += [
-            'gecko/ProfileGatherer.cpp',
+            'gecko/nsProfiler.cpp',
         ]
     else:
         UNIFIED_SOURCES += [
-            'gecko/ProfileGatherer.cpp',
+            'gecko/nsProfiler.cpp',
         ]
 
     if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
         UNIFIED_SOURCES += [
             'lul/AutoObjectMapper.cpp',
             'lul/LulCommon.cpp',
             'lul/LulDwarf.cpp',
             'lul/LulDwarfSummariser.cpp',