Bug 1355634 - Use MozPromise to make ProfileGatherer more generic. r=njn
authorMarkus Stange <mstange@themasta.com>
Wed, 12 Apr 2017 16:57:58 -0400
changeset 353347 3303122705233a99269a52f8ee15648bd0f26e46
parent 353346 853ace07ede93a984f7253447f80d0d098cd5a89
child 353348 05f0e6fea003df5c3d81fd2254486394ef327cf7
push id31661
push userarchaeopteryx@coole-files.de
push dateSat, 15 Apr 2017 18:00:16 +0000
treeherdermozilla-central@d65b53cf8fd9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1355634
milestone55.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 1355634 - Use MozPromise to make ProfileGatherer more generic. r=njn 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',