Bug 1707954: Part 3 - Add eager MTA creation to mscom::EnsureMTA; r=Jamie

* We make `EnsureMTA`'s default constructor `private`, and `ProcessRuntime` a friend.
  `ProcessRuntime` calls this to eagerly create the MTA.
* The default constructor uses the new-ish `CoIncrementMTAUsage` to create the
  MTA without requiring a dedicated thread (when available). Otherwise we
  fall back to the traditional method. In the latter case, we synchronously
  wait for the initialization to complete so that we are guaranteed to have
  an MTA when we return.
* Some minor refactoring to make it easier to do the sync wait in the
  default constructor. I also renamed a couple of things just to make them
  more clear.

Differential Revision: https://phabricator.services.mozilla.com/D113562
This commit is contained in:
Aaron Klotz 2021-06-09 20:28:06 +00:00
Родитель 43d03f255a
Коммит 2bac3c5a84
2 изменённых файлов: 141 добавлений и 56 удалений

Просмотреть файл

@ -6,14 +6,25 @@
#include "mozilla/mscom/EnsureMTA.h"
#include "mozilla/Assertions.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/mscom/Utils.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/StaticLocalPtr.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "private/pprthred.h"
#include <combaseapi.h>
#if (NTDDI_VERSION < NTDDI_WIN8)
// Win8+ API that we use very carefully
DECLARE_HANDLE(CO_MTA_USAGE_COOKIE);
HRESULT WINAPI CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie);
#endif // (NTDDI_VERSION < NTDDI_WIN8)
namespace {
class EnterMTARunnable : public mozilla::Runnable {
@ -31,8 +42,8 @@ class BackgroundMTAData {
public:
BackgroundMTAData() {
nsCOMPtr<nsIRunnable> runnable = new EnterMTARunnable();
mozilla::DebugOnly<nsresult> rv =
NS_NewNamedThread("COM MTA", getter_AddRefs(mThread), runnable);
mozilla::DebugOnly<nsresult> rv = NS_NewNamedThread(
"COM MTA", getter_AddRefs(mThread), runnable.forget());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_NewNamedThread failed");
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
@ -58,6 +69,54 @@ class BackgroundMTAData {
namespace mozilla {
namespace mscom {
EnsureMTA::EnsureMTA() {
MOZ_ASSERT(NS_IsMainThread());
// It is possible that we're running so early that we might need to start
// the thread manager ourselves. We do this here to guarantee that we have
// the ability to start the persistent MTA thread at any moment beyond this
// point.
nsresult rv = nsThreadManager::get().Init();
// We intentionally don't check rv unless we need it when
// CoIncremementMTAUsage is unavailable.
// This API is only available beginning with Windows 8. Even though this
// constructor will only be called once, we intentionally use
// StaticDynamicallyLinkedFunctionPtr here to hang onto the ole32 module.
static const StaticDynamicallyLinkedFunctionPtr<
decltype(&::CoIncrementMTAUsage)>
pCoIncrementMTAUsage(L"ole32.dll", "CoIncrementMTAUsage");
if (pCoIncrementMTAUsage) {
// Calling this function initializes the MTA without needing to explicitly
// create a thread and call CoInitializeEx to do it.
// We don't retain the cookie because once we've incremented the MTA, we
// leave it that way for the lifetime of the process.
CO_MTA_USAGE_COOKIE mtaCookie = nullptr;
HRESULT hr = pCoIncrementMTAUsage(&mtaCookie);
MOZ_ASSERT(SUCCEEDED(hr));
if (SUCCEEDED(hr)) {
return;
}
}
// In the fallback case, we simply initialize our persistent MTA thread.
// Make sure thread manager init succeeded before trying to initialize the
// persistent MTA thread.
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
if (NS_FAILED(rv)) {
return;
}
// Before proceeding any further, pump a runnable through the persistent MTA
// thread to ensure that it is up and running and has finished initializing
// the multi-threaded apartment.
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
"EnsureMTA::EnsureMTA()",
[]() { MOZ_RELEASE_ASSERT(IsCurrentThreadExplicitMTA()); }));
SyncDispatchToPersistentThread(runnable);
}
/* static */
RefPtr<EnsureMTA::CreateInstanceAgileRefPromise>
EnsureMTA::CreateInstanceInternal(REFCLSID aClsid, REFIID aIid) {
@ -113,14 +172,14 @@ RefPtr<EnsureMTA::CreateInstanceAgileRefPromise> EnsureMTA::CreateInstance(
return CreateInstanceInternal(localClsid, localIid);
};
nsCOMPtr<nsIThread> mtaThread(GetMTAThread());
nsCOMPtr<nsIThread> mtaThread(GetPersistentMTAThread());
return InvokeAsync(mtaThread->SerialEventTarget(), __func__,
std::move(invoker));
}
/* static */
nsCOMPtr<nsIThread> EnsureMTA::GetMTAThread() {
nsCOMPtr<nsIThread> EnsureMTA::GetPersistentMTAThread() {
static StaticLocalAutoPtr<BackgroundMTAData> sMTAData(
[]() -> BackgroundMTAData* {
BackgroundMTAData* bgData = new BackgroundMTAData();
@ -136,7 +195,7 @@ nsCOMPtr<nsIThread> EnsureMTA::GetMTAThread() {
SchedulerGroup::Dispatch(
TaskCategory::Other,
NS_NewRunnableFunction("mscom::EnsureMTA::GetMTAThread",
NS_NewRunnableFunction("mscom::EnsureMTA::GetPersistentMTAThread",
std::move(setClearOnShutdown)));
return bgData;
@ -147,5 +206,57 @@ nsCOMPtr<nsIThread> EnsureMTA::GetMTAThread() {
return sMTAData->GetThread();
}
/* static */
void EnsureMTA::SyncDispatchToPersistentThread(nsIRunnable* aRunnable) {
nsCOMPtr<nsIThread> thread(GetPersistentMTAThread());
MOZ_ASSERT(thread);
if (!thread) {
return;
}
// Note that, due to APC dispatch, we might reenter this function while we
// wait on this event. We therefore need a unique event object for each
// entry into this function. If perf becomes an issue then we will want to
// maintain an array of events where the Nth event is unique to the Nth
// reentry.
nsAutoHandle event(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
if (!event) {
return;
}
HANDLE eventHandle = event.get();
auto eventSetter = [&aRunnable, eventHandle]() -> void {
aRunnable->Run();
::SetEvent(eventHandle);
};
nsresult rv = thread->Dispatch(
NS_NewRunnableFunction("mscom::EnsureMTA::SyncDispatchToPersistentThread",
std::move(eventSetter)),
NS_DISPATCH_NORMAL);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (NS_FAILED(rv)) {
return;
}
const BOOL alertable = XRE_IsContentProcess() && NS_IsMainThread();
DWORD waitResult;
while ((waitResult = ::WaitForSingleObjectEx(event, INFINITE, alertable)) ==
WAIT_IO_COMPLETION) {
}
MOZ_ASSERT(waitResult == WAIT_OBJECT_0);
}
/**
* While this function currently appears to be redundant, it may become more
* sophisticated in the future. For example, we could optionally dispatch to an
* MTA context if we wanted to utilize the MTA thread pool.
*/
/* static */
void EnsureMTA::SyncDispatch(nsCOMPtr<nsIRunnable>&& aRunnable, Option aOpt) {
SyncDispatchToPersistentThread(aRunnable);
}
} // namespace mscom
} // namespace mozilla

Просмотреть файл

@ -40,74 +40,37 @@ struct PreservedStreamDeleter;
} // namespace detail
class ProcessRuntime;
// This class is OK to use as a temporary on the stack.
class MOZ_STACK_CLASS EnsureMTA final {
public:
/**
* This constructor just ensures that the MTA thread is up and running.
*/
EnsureMTA() {
nsCOMPtr<nsIThread> thread = GetMTAThread();
MOZ_ASSERT(thread);
Unused << thread;
}
enum class Option {
Default,
// Forcibly dispatch to the thread returned by GetMTAThread(), even if the
// current thread is already inside a MTA.
ForceDispatch,
// Forcibly dispatch to the thread returned by GetPersistentMTAThread(),
// even if the current thread is already inside a MTA.
ForceDispatchToPersistentThread,
};
/**
* Synchronously run |aClosure| on a thread living in the COM multithreaded
* apartment. If the current thread lives inside the COM MTA, then it runs
* |aClosure| immediately unless |aOpt| == Option::ForceDispatch.
* |aClosure| immediately unless |aOpt| ==
* Option::ForceDispatchToPersistentThread.
*/
template <typename FuncT>
explicit EnsureMTA(FuncT&& aClosure, Option aOpt = Option::Default) {
if (aOpt != Option::ForceDispatch && IsCurrentThreadMTA()) {
if (aOpt != Option::ForceDispatchToPersistentThread &&
IsCurrentThreadMTA()) {
// We're already on the MTA, we can run aClosure directly
aClosure();
return;
}
// In this case we need to run aClosure on a background thread in the MTA
nsCOMPtr<nsIThread> thread = GetMTAThread();
MOZ_ASSERT(thread);
if (!thread) {
return;
}
// Note that we might reenter the EnsureMTA constructor while we wait on
// this event due to APC dispatch, therefore we need a unique event object
// for each entry. If perf becomes an issue then we will want to maintain
// an array of events where the Nth event is unique to the Nth reentry.
nsAutoHandle event(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
if (!event) {
return;
}
HANDLE eventHandle = event.get();
auto eventSetter = [&aClosure, eventHandle]() -> void {
aClosure();
::SetEvent(eventHandle);
};
nsresult rv = thread->Dispatch(
NS_NewRunnableFunction("EnsureMTA", std::move(eventSetter)),
NS_DISPATCH_NORMAL);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (NS_FAILED(rv)) {
return;
}
DWORD waitResult;
while ((waitResult = ::WaitForSingleObjectEx(event, INFINITE, TRUE)) ==
WAIT_IO_COMPLETION) {
}
MOZ_ASSERT(waitResult == WAIT_OBJECT_0);
nsCOMPtr<nsIRunnable> runnable(
NS_NewRunnableFunction("EnsureMTA::EnsureMTA", std::move(aClosure)));
SyncDispatch(std::move(runnable), aOpt);
}
using CreateInstanceAgileRefPromise =
@ -173,7 +136,10 @@ class MOZ_STACK_CLASS EnsureMTA final {
static RefPtr<CreateInstanceAgileRefPromise> CreateInstanceInternal(
REFCLSID aClsid, REFIID aIid);
static nsCOMPtr<nsIThread> GetMTAThread();
static nsCOMPtr<nsIThread> GetPersistentMTAThread();
static void SyncDispatch(nsCOMPtr<nsIRunnable>&& aRunnable, Option aOpt);
static void SyncDispatchToPersistentThread(nsIRunnable* aRunnable);
// The following function is private in order to force any consumers to be
// declared as friends of EnsureMTA. The intention is to prevent
@ -186,7 +152,7 @@ class MOZ_STACK_CLASS EnsureMTA final {
return;
}
nsCOMPtr<nsIThread> thread(GetMTAThread());
nsCOMPtr<nsIThread> thread(GetPersistentMTAThread());
MOZ_ASSERT(thread);
if (!thread) {
return;
@ -199,6 +165,14 @@ class MOZ_STACK_CLASS EnsureMTA final {
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
/**
* This constructor just ensures that the MTA is up and running. This should
* only be called by ProcessRuntime.
*/
EnsureMTA();
friend class mozilla::mscom::ProcessRuntime;
template <typename T>
friend struct mozilla::mscom::detail::MTADelete;