Bug 1346356 - Do not notify observers while gPSMutex is locked. r?njn draft
authorMarkus Stange <mstange@themasta.com>
Sat, 11 Mar 2017 17:53:40 -0500
changeset 497173 f72e8ea1f8b2ba983cb5aa746b485c3bb3a0d432
parent 496979 4ceb9062ea8f4113bfd1b3536ace4a840a72faa7
child 497175 a8257203d5f191ca8f9eb5c26697ecc0766347ec
child 497791 93b75cb48dde8ad891fca29e4c3d1be0fb033717
child 497820 d21c128eec0cd6de68e49404e1588f3690e7aea0
push id48813
push userbmo:mstange@themasta.com
push dateSat, 11 Mar 2017 23:12:44 +0000
reviewersnjn
bugs1346356
milestone55.0a1
Bug 1346356 - Do not notify observers while gPSMutex is locked. r?njn Observers that are implemented in JavaScript can trigger arbitrary interactions with the profiler, most of which will attempt to lock gPSMutex again, which would result in a deadlock. This patch fixes profiler_start, profiler_stop, profiler_shutdown, profiler_pause and profiler_resume. It does not fix StreamJSON which notifies "profiler-subprocess" observers while the mutex is locked. That notification is used as a way to synchronously ask observers to add their JSON to the current place in the JSON buffer, so moving it to a different place would break things. Luckily, our current "profiler-subprocess" observers are in C++ code that currently does not try to touch the profiler. MozReview-Commit-ID: 5Ms3ladQf7j
tools/profiler/core/platform.cpp
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -1836,27 +1836,29 @@ profiler_shutdown()
   LOG("BEGIN profiler_shutdown");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
   // If the profiler is active we must get a handle to the SamplerThread before
   // gPS is destroyed, in order to call Join().
   SamplerThread* samplerThread = nullptr;
+  bool wasActive = false;
   {
     PS::AutoLock lock(gPSMutex);
 
     // Save the profile on shutdown if requested.
     if (gPS->IsActive(lock)) {
       const char* filename = getenv("MOZ_PROFILER_SHUTDOWN");
       if (filename) {
         locked_profiler_save_profile_to_file(lock, filename);
       }
 
       samplerThread = locked_profiler_stop(lock);
+      wasActive = true;
     }
 
     set_stderr_callback(nullptr);
 
     PS::ThreadVector& threads = gPS->Threads(lock);
     while (threads.size() > 0) {
       delete threads.back();
       threads.pop_back();
@@ -1881,16 +1883,25 @@ profiler_shutdown()
     delete tlsPseudoStack.get();
     tlsPseudoStack.set(nullptr);
 
 #ifdef MOZ_TASK_TRACER
     mozilla::tasktracer::ShutdownTaskTracer();
 #endif
   }
 
+  // We call NotifyObservers() with gPSMutex unlocked. The comment in
+  // profiler_stop() explains why.
+  if (wasActive && CanNotifyObservers()) {
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
+    }
+  }
+
   // We call Join() with gPSMutex unlocked. The comment in profiler_stop()
   // explains why.
   if (samplerThread) {
     samplerThread->Join();
     delete samplerThread;
   }
 
   LOG("END   profiler_shutdown");
@@ -2294,83 +2305,91 @@ locked_profiler_start(PS::LockRef aLock,
 
   if (featureMainThreadIO) {
     auto interposeObserver = new mozilla::ProfilerIOInterposeObserver();
     gPS->SetInterposeObserver(aLock, interposeObserver);
     mozilla::IOInterposer::Register(mozilla::IOInterposeObserver::OpAll,
                                     interposeObserver);
   }
 
+  LOG("END   locked_profiler_start");
+}
+
+void
+profiler_start(int aEntries, double aInterval,
+               const char** aFeatures, uint32_t aFeatureCount,
+               const char** aThreadNameFilters, uint32_t aFilterCount)
+{
+  LOG("BEGIN profiler_start");
+
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  bool wasActive = false;
+
+  {
+    PS::AutoLock lock(gPSMutex);
+
+    // Initialize if necessary.
+    if (!gPS) {
+      profiler_init(nullptr);
+    }
+
+    // Reset the current state if the profiler is running.
+    if (gPS->IsActive(lock)) {
+      locked_profiler_stop(lock);
+      wasActive = true;
+    }
+
+    locked_profiler_start(lock, aEntries, aInterval, aFeatures, aFeatureCount,
+                          aThreadNameFilters, aFilterCount);
+  }
+
+  // Notify observers that the profiler has been started (and possibly stopped
+  // before).
+  // This needs to be done outside of the PS lock, because observers can call
+  // back into the profiler, for example in order to capture stacks or to add
+  // markers, and doing so with the PS lock held would lead to a deadlock.
   if (CanNotifyObservers()) {
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     if (os) {
+      if (wasActive) {
+        os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
+      }
+
       nsTArray<nsCString> featuresArray;
       for (size_t i = 0; i < aFeatureCount; ++i) {
         featuresArray.AppendElement(aFeatures[i]);
       }
 
       nsTArray<nsCString> threadNameFiltersArray;
       for (size_t i = 0; i < aFilterCount; ++i) {
         threadNameFiltersArray.AppendElement(aThreadNameFilters[i]);
       }
 
       nsCOMPtr<nsIProfilerStartParams> params =
-        new nsProfilerStartParams(entries, interval, featuresArray,
+        new nsProfilerStartParams(aEntries, aInterval, featuresArray,
                                   threadNameFiltersArray);
       os->NotifyObservers(params, "profiler-started", nullptr);
     }
   }
 
-  LOG("END   locked_profiler_start");
-}
-
-void
-profiler_start(int aEntries, double aInterval,
-               const char** aFeatures, uint32_t aFeatureCount,
-               const char** aThreadNameFilters, uint32_t aFilterCount)
-{
-  LOG("BEGIN profiler_start");
-
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
-  PS::AutoLock lock(gPSMutex);
-
-  // Initialize if necessary.
-  if (!gPS) {
-    profiler_init(nullptr);
-  }
-
-  // Reset the current state if the profiler is running.
-  if (gPS->IsActive(lock)) {
-    locked_profiler_stop(lock);
-  }
-
-  locked_profiler_start(lock, aEntries, aInterval, aFeatures, aFeatureCount,
-                        aThreadNameFilters, aFilterCount);
-
   LOG("END   profiler_start");
 }
 
 static SamplerThread*
 locked_profiler_stop(PS::LockRef aLock)
 {
   LOG("BEGIN locked_profiler_stop");
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS && gPS->IsActive(aLock));
 
   // We clear things in roughly reverse order to their setting in
   // locked_profiler_start().
 
-  if (CanNotifyObservers()) {
-    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    if (os)
-      os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
-  }
-
   mozilla::IOInterposer::Unregister(mozilla::IOInterposeObserver::OpAll,
                                     gPS->InterposeObserver(aLock));
   gPS->SetInterposeObserver(aLock, nullptr);
 
   // The Stop() call doesn't actually stop Run(); that happens in this
   // function's caller, via Join(). Stop() just gives the SamplerThread a chance
   // to do some cleanup with gPSMutex locked.
   SamplerThread* samplerThread = gPS->SamplerThread(aLock);
@@ -2454,16 +2473,27 @@ profiler_stop()
     if (!gPS->IsActive(lock)) {
       LOG("END   profiler_stop: inactive");
       return;
     }
 
     samplerThread = locked_profiler_stop(lock);
   }
 
+  // Notify observers that the profiler has been stopped.
+  // This needs to be done outside of the PS lock, because observers can call
+  // back into the profiler, for example to capture stacks or to add markers,
+  // and doing so with the PS lock held would lead to a deadlock.
+  if (CanNotifyObservers()) {
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
+    }
+  }
+
   // We call Join() with gPSMutex unlocked. Otherwise we would get a deadlock:
   // we would be waiting here with gPSMutex locked for SamplerThread::Run() to
   // return so the Join() can complete, but Run() needs to lock gPSMutex to
   // return.
   //
   // Because this call occurs with gPSMutex unlocked, it -- including the final
   // iteration of Run()'s loop -- must be able detect deactivation and return
   // in a way that's safe with respect to other gPSMutex-locking operations
@@ -2489,47 +2519,52 @@ profiler_is_paused()
   return gPS->IsPaused(lock);
 }
 
 void
 profiler_pause()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
-
-  PS::AutoLock lock(gPSMutex);
-
-  if (!gPS->IsActive(lock)) {
-    return;
+  {
+    PS::AutoLock lock(gPSMutex);
+
+    if (!gPS->IsActive(lock)) {
+      return;
+    }
+
+    gPS->SetIsPaused(lock, true);
   }
 
-  gPS->SetIsPaused(lock, true);
-
+  // Notify observers with gPSMutex unlocked.
   if (CanNotifyObservers()) {
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     if (os) {
       os->NotifyObservers(nullptr, "profiler-paused", nullptr);
     }
   }
 }
 
 void
 profiler_resume()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(gPS);
 
-  PS::AutoLock lock(gPSMutex);
-
-  if (!gPS->IsActive(lock)) {
-    return;
+  {
+    PS::AutoLock lock(gPSMutex);
+
+    if (!gPS->IsActive(lock)) {
+      return;
+    }
+
+    gPS->SetIsPaused(lock, false);
   }
 
-  gPS->SetIsPaused(lock, false);
-
+  // Notify observers with gPSMutex unlocked.
   if (CanNotifyObservers()) {
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     if (os) {
       os->NotifyObservers(nullptr, "profiler-resumed", nullptr);
     }
   }
 }