Bug 1311834: Make MainThreadInvoker use a spin loop on multiprocessor machines; r?jimm draft
authorAaron Klotz <aklotz@mozilla.com>
Tue, 25 Oct 2016 15:43:40 -0600
changeset 429425 62fad969c803fcf430b5ec14ac5c7f4bbe77b9b3
parent 429424 538e4efdd17b48404b32606ce8e5cf2274036d2c
child 534970 412a0cbea06517c97a02ac12582d503cb05f5e7a
push id33570
push useraklotz@mozilla.com
push dateTue, 25 Oct 2016 21:44:55 +0000
reviewersjimm
bugs1311834
milestone52.0a1
Bug 1311834: Make MainThreadInvoker use a spin loop on multiprocessor machines; r?jimm MozReview-Commit-ID: 5xKdm7Z4oKs
ipc/mscom/MainThreadInvoker.cpp
ipc/mscom/MainThreadInvoker.h
ipc/mscom/moz.build
--- a/ipc/mscom/MainThreadInvoker.cpp
+++ b/ipc/mscom/MainThreadInvoker.cpp
@@ -4,45 +4,99 @@
  * 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 "mozilla/mscom/MainThreadInvoker.h"
 
 #include "GeckoProfiler.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/HangMonitor.h"
 #include "mozilla/RefPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSystemInfo.h"
 #include "private/prpriv.h" // For PR_GetThreadID
 #include "WinUtils.h"
 
+// This gives us compiler intrinsics for the x86 PAUSE instruction
+#if defined(_MSC_VER)
+#include <intrin.h>
+#pragma intrinsic(_mm_pause)
+#define CPU_PAUSE() _mm_pause()
+#elif defined(__GNUC__) || defined(__clang__)
+#define CPU_PAUSE() __builtin_ia32_pause()
+#endif
+
+static bool sIsMulticore;
+
 namespace {
 
-class SyncRunnable : public mozilla::Runnable
+/**
+ * SyncRunnable implements different code paths depending on whether or not
+ * we are running on a multiprocessor system. In the multiprocessor case, we
+ * leave the thread in a spin loop while waiting for the main thread to execute
+ * our runnable. Since spinning is pointless in the uniprocessor case, we block
+ * on an event that is set by the main thread once it has finished the runnable.
+ */
+class MOZ_RAII SyncRunnable
 {
 public:
-  SyncRunnable(HANDLE aEvent, already_AddRefed<nsIRunnable>&& aRunnable)
-    : mDoneEvent(aEvent)
+  explicit SyncRunnable(already_AddRefed<nsIRunnable>&& aRunnable)
+    : mDoneEvent(sIsMulticore ? nullptr :
+                 ::CreateEventW(nullptr, FALSE, FALSE, nullptr))
+    , mDone(false)
     , mRunnable(aRunnable)
   {
-    MOZ_ASSERT(aEvent);
+    MOZ_ASSERT(sIsMulticore || mDoneEvent);
     MOZ_ASSERT(mRunnable);
   }
 
-  NS_IMETHOD Run() override
+  ~SyncRunnable()
+  {
+    if (mDoneEvent) {
+      ::CloseHandle(mDoneEvent);
+    }
+  }
+
+  void Run()
   {
     mRunnable->Run();
-    ::SetEvent(mDoneEvent);
-    return NS_OK;
+
+    if (mDoneEvent) {
+      ::SetEvent(mDoneEvent);
+    } else {
+      mDone = true;
+    }
+  }
+
+  bool WaitUntilComplete()
+  {
+    if (mDoneEvent) {
+      HANDLE handles[] = {mDoneEvent,
+                          mozilla::mscom::MainThreadInvoker::GetTargetThread()};
+      DWORD waitResult = ::WaitForMultipleObjects(mozilla::ArrayLength(handles),
+                                                  handles, FALSE, INFINITE);
+      return waitResult == WAIT_OBJECT_0;
+    }
+
+    while (!mDone) {
+      // The PAUSE instruction is a hint to the CPU that we're doing a spin
+      // loop. It is a no-op on older processors that don't support it, so
+      // it is safe to use here without any CPUID checks.
+      CPU_PAUSE();
+    }
+    return true;
   }
 
 private:
   HANDLE                mDoneEvent;
+  mozilla::Atomic<bool> mDone;
   nsCOMPtr<nsIRunnable> mRunnable;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace mscom {
 
@@ -51,84 +105,76 @@ HANDLE MainThreadInvoker::sMainThread = 
 /* static */ bool
 MainThreadInvoker::InitStatics()
 {
   nsCOMPtr<nsIThread> mainThread;
   nsresult rv = ::NS_GetMainThread(getter_AddRefs(mainThread));
   if (NS_FAILED(rv)) {
     return false;
   }
+
   PRThread* mainPrThread = nullptr;
   rv = mainThread->GetPRThread(&mainPrThread);
   if (NS_FAILED(rv)) {
     return false;
   }
+
   PRUint32 tid = ::PR_GetThreadID(mainPrThread);
   sMainThread = ::OpenThread(SYNCHRONIZE | THREAD_SET_CONTEXT, FALSE, tid);
+
+  nsCOMPtr<nsIPropertyBag2> infoService = do_GetService(NS_SYSTEMINFO_CONTRACTID);
+  if (infoService) {
+    uint32_t cpuCount;
+    nsresult rv = infoService->GetPropertyAsUint32(NS_LITERAL_STRING("cpucount"),
+                                                   &cpuCount);
+    sIsMulticore = NS_SUCCEEDED(rv) && cpuCount > 1;
+  }
+
   return !!sMainThread;
 }
 
 MainThreadInvoker::MainThreadInvoker()
-  : mDoneEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr))
 {
   static const bool gotStatics = InitStatics();
   MOZ_ASSERT(gotStatics);
 }
 
-MainThreadInvoker::~MainThreadInvoker()
-{
-  if (mDoneEvent) {
-    ::CloseHandle(mDoneEvent);
-  }
-}
-
 bool
-MainThreadInvoker::WaitForCompletion(DWORD aTimeout)
-{
-  HANDLE handles[] = {mDoneEvent, sMainThread};
-  DWORD waitResult = ::WaitForMultipleObjects(ArrayLength(handles), handles,
-                                              FALSE, aTimeout);
-  return waitResult == WAIT_OBJECT_0;
-}
-
-bool
-MainThreadInvoker::Invoke(already_AddRefed<nsIRunnable>&& aRunnable,
-                          DWORD aTimeout)
+MainThreadInvoker::Invoke(already_AddRefed<nsIRunnable>&& aRunnable)
 {
   nsCOMPtr<nsIRunnable> runnable(Move(aRunnable));
   if (!runnable) {
     return false;
   }
+
   if (NS_IsMainThread()) {
     runnable->Run();
     return true;
   }
-  RefPtr<SyncRunnable> wrappedRunnable(new SyncRunnable(mDoneEvent,
-                                                        runnable.forget()));
-  // Make sure that wrappedRunnable remains valid while sitting in the APC queue
-  wrappedRunnable->AddRef();
+
+  SyncRunnable syncRunnable(runnable.forget());
+
   if (!::QueueUserAPC(&MainThreadAPC, sMainThread,
-                      reinterpret_cast<UINT_PTR>(wrappedRunnable.get()))) {
-    // Enqueue failed so cancel the above AddRef
-    wrappedRunnable->Release();
+                      reinterpret_cast<UINT_PTR>(&syncRunnable))) {
     return false;
   }
+
   // We should ensure a call to NtTestAlert() is made on the main thread so
   // that the main thread will check for APCs during event processing. If we
   // omit this then the main thread will not check its APC queue until it is
   // idle.
   widget::WinUtils::SetAPCPending();
-  return WaitForCompletion(aTimeout);
+
+  return syncRunnable.WaitUntilComplete();
 }
 
 /* static */ VOID CALLBACK
 MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam)
 {
   GeckoProfilerWakeRAII wakeProfiler;
   mozilla::HangMonitor::NotifyActivity(mozilla::HangMonitor::kGeneralActivity);
   MOZ_ASSERT(NS_IsMainThread());
-  RefPtr<SyncRunnable> runnable(already_AddRefed<SyncRunnable>(
-                                  reinterpret_cast<SyncRunnable*>(aParam)));
+  auto runnable = reinterpret_cast<SyncRunnable*>(aParam);
   runnable->Run();
 }
 
 } // namespace mscom
 } // namespace mozilla
--- a/ipc/mscom/MainThreadInvoker.h
+++ b/ipc/mscom/MainThreadInvoker.h
@@ -16,29 +16,23 @@ class nsIRunnable;
 
 namespace mozilla {
 namespace mscom {
 
 class MainThreadInvoker
 {
 public:
   MainThreadInvoker();
-  ~MainThreadInvoker();
 
-  bool WaitForCompletion(DWORD aTimeout = INFINITE);
-  bool Invoke(already_AddRefed<nsIRunnable>&& aRunnable,
-              DWORD aTimeout = INFINITE);
-  HANDLE GetTargetThread() const { return sMainThread; }
+  bool Invoke(already_AddRefed<nsIRunnable>&& aRunnable);
+  static HANDLE GetTargetThread() { return sMainThread; }
 
 private:
   static bool InitStatics();
   static VOID CALLBACK MainThreadAPC(ULONG_PTR aParam);
 
-  HANDLE  mDoneEvent;
-
   static HANDLE sMainThread;
-  static StaticRefPtr<nsIRunnable> sAlertRunnable;
 };
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_MainThreadInvoker_h
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -56,14 +56,15 @@ if CONFIG['ACCESSIBILITY']:
         'DispatchForwarder.cpp',
         'InterceptorLog.cpp',
         'MainThreadHandoff.cpp',
         'MainThreadInvoker.cpp',
         'ThreadLocalObject.cpp',
     ]
 
 LOCAL_INCLUDES += [
+    '/xpcom/base',
     '/xpcom/build',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'