Bug 1263224: Adds MainThreadInvoker class for posting Asynchronous Procedure Calls to the main thread; r?jimm draft
authorAaron Klotz <aklotz@mozilla.com>
Tue, 19 Jul 2016 13:35:36 -0600
changeset 393335 ba95eef4285f1cebb575b0c071e52399416a27de
parent 393334 0fdf654eee404ddca46d4a4987e28e95557bf0bd
child 393336 5f5bd38f4d3bd68bfd1d0c39ee922d378ee41c1d
push id24290
push useraklotz@mozilla.com
push dateWed, 27 Jul 2016 18:19:09 +0000
reviewersjimm
bugs1263224
milestone50.0a1
Bug 1263224: Adds MainThreadInvoker class for posting Asynchronous Procedure Calls to the main thread; r?jimm MozReview-Commit-ID: 6Be0p0z9CXI
ipc/mscom/MainThreadInvoker.cpp
ipc/mscom/MainThreadInvoker.h
ipc/mscom/moz.build
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/MainThreadInvoker.cpp
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/mscom/MainThreadInvoker.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RefPtr.h"
+#include "private/prpriv.h" // For PR_GetThreadID
+
+#include <winternl.h> // For NTSTATUS and NTAPI
+
+namespace {
+
+class SyncRunnable : public mozilla::Runnable
+{
+public:
+  SyncRunnable(HANDLE aEvent, already_AddRefed<nsIRunnable>&& aRunnable)
+    : mDoneEvent(aEvent)
+    , mRunnable(aRunnable)
+  {
+    MOZ_ASSERT(aEvent);
+    MOZ_ASSERT(mRunnable);
+  }
+
+  NS_IMETHOD Run() override
+  {
+    mRunnable->Run();
+    ::SetEvent(mDoneEvent);
+    return NS_OK;
+  }
+
+private:
+  HANDLE                mDoneEvent;
+  nsCOMPtr<nsIRunnable> mRunnable;
+};
+
+typedef NTSTATUS (NTAPI* NtTestAlertPtr)(VOID);
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace mscom {
+
+HANDLE MainThreadInvoker::sMainThread = nullptr;
+StaticRefPtr<nsIRunnable> MainThreadInvoker::sAlertRunnable;
+
+/* 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);
+  if (!sMainThread) {
+    return false;
+  }
+  NtTestAlertPtr NtTestAlert =
+    reinterpret_cast<NtTestAlertPtr>(
+        ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert"));
+  sAlertRunnable = ::NS_NewRunnableFunction([NtTestAlert]() -> void {
+    // We're using NtTestAlert() instead of SleepEx() so that the main thread
+    // never gives up its quantum if there are no APCs pending.
+    NtTestAlert();
+  }).take();
+  if (sAlertRunnable) {
+    ClearOnShutdown(&sAlertRunnable);
+  }
+  return !!sAlertRunnable;
+}
+
+MainThreadInvoker::MainThreadInvoker()
+  : mDoneEvent(::CreateEvent(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)
+{
+  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();
+  if (!::QueueUserAPC(&MainThreadAPC, sMainThread,
+                      reinterpret_cast<UINT_PTR>(wrappedRunnable.get()))) {
+    // Enqueue failed so cancel the above AddRef
+    wrappedRunnable->Release();
+    return false;
+  }
+  // We should enqueue a call to NtTestAlert() 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. Note that failing to
+  // dispatch this event is non-fatal, but it will delay execution of the APC.
+  NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(sAlertRunnable)));
+  return WaitForCompletion(aTimeout);
+}
+
+/* static */ VOID CALLBACK
+MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  RefPtr<SyncRunnable> runnable(already_AddRefed<SyncRunnable>(
+                                  reinterpret_cast<SyncRunnable*>(aParam)));
+  runnable->Run();
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/MainThreadInvoker.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_mscom_MainThreadInvoker_h
+#define mozilla_mscom_MainThreadInvoker_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/StaticPtr.h"
+
+#include <windows.h>
+
+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; }
+
+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
@@ -4,31 +4,33 @@
 # 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/.
 
 EXPORTS.mozilla.mscom += [
     'COMApartmentRegion.h',
     'COMPtrHolder.h',
     'EnsureMTA.h',
     'InterceptorLog.h',
+    'MainThreadInvoker.h',
     'MainThreadRuntime.h',
     'ProxyStream.h',
     'Ptr.h',
     'Registration.h',
     'Utils.h',
 ]
 
 SOURCES += [
     'Registration.cpp',
     'Utils.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'EnsureMTA.cpp',
     'InterceptorLog.cpp',
+    'MainThreadInvoker.cpp',
     'MainThreadRuntime.cpp',
     'ProxyStream.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/xpcom/build',
 ]