Bug 1311834: Make MainThreadInvoker use a spin loop on multiprocessor machines; r=jimm

MozReview-Commit-ID: 5xKdm7Z4oKs

--HG--
extra : rebase_source : f71e3b762501d8e24446ba8913c841cfe03a277b
This commit is contained in:
Aaron Klotz 2016-10-25 15:43:40 -06:00
parent 78c3624c28
commit 8cfa990adf
3 changed files with 85 additions and 44 deletions

View File

@ -9,35 +9,89 @@
#include "GeckoProfiler.h" #include "GeckoProfiler.h"
#include "MainThreadUtils.h" #include "MainThreadUtils.h"
#include "mozilla/Assertions.h" #include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/ClearOnShutdown.h" #include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h" #include "mozilla/DebugOnly.h"
#include "mozilla/HangMonitor.h" #include "mozilla/HangMonitor.h"
#include "mozilla/RefPtr.h" #include "mozilla/RefPtr.h"
#include "nsServiceManagerUtils.h"
#include "nsSystemInfo.h"
#include "private/prpriv.h" // For PR_GetThreadID #include "private/prpriv.h" // For PR_GetThreadID
#include "WinUtils.h" #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 { 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: public:
SyncRunnable(HANDLE aEvent, already_AddRefed<nsIRunnable>&& aRunnable) explicit SyncRunnable(already_AddRefed<nsIRunnable>&& aRunnable)
: mDoneEvent(aEvent) : mDoneEvent(sIsMulticore ? nullptr :
::CreateEventW(nullptr, FALSE, FALSE, nullptr))
, mDone(false)
, mRunnable(aRunnable) , mRunnable(aRunnable)
{ {
MOZ_ASSERT(aEvent); MOZ_ASSERT(sIsMulticore || mDoneEvent);
MOZ_ASSERT(mRunnable); MOZ_ASSERT(mRunnable);
} }
NS_IMETHOD Run() override ~SyncRunnable()
{
if (mDoneEvent) {
::CloseHandle(mDoneEvent);
}
}
void Run()
{ {
mRunnable->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: private:
HANDLE mDoneEvent; HANDLE mDoneEvent;
mozilla::Atomic<bool> mDone;
nsCOMPtr<nsIRunnable> mRunnable; nsCOMPtr<nsIRunnable> mRunnable;
}; };
@ -56,67 +110,60 @@ MainThreadInvoker::InitStatics()
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return false; return false;
} }
PRThread* mainPrThread = nullptr; PRThread* mainPrThread = nullptr;
rv = mainThread->GetPRThread(&mainPrThread); rv = mainThread->GetPRThread(&mainPrThread);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return false; return false;
} }
PRUint32 tid = ::PR_GetThreadID(mainPrThread); PRUint32 tid = ::PR_GetThreadID(mainPrThread);
sMainThread = ::OpenThread(SYNCHRONIZE | THREAD_SET_CONTEXT, FALSE, tid); 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; return !!sMainThread;
} }
MainThreadInvoker::MainThreadInvoker() MainThreadInvoker::MainThreadInvoker()
: mDoneEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr))
{ {
static const bool gotStatics = InitStatics(); static const bool gotStatics = InitStatics();
MOZ_ASSERT(gotStatics); MOZ_ASSERT(gotStatics);
} }
MainThreadInvoker::~MainThreadInvoker()
{
if (mDoneEvent) {
::CloseHandle(mDoneEvent);
}
}
bool bool
MainThreadInvoker::WaitForCompletion(DWORD aTimeout) MainThreadInvoker::Invoke(already_AddRefed<nsIRunnable>&& aRunnable)
{
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)); nsCOMPtr<nsIRunnable> runnable(Move(aRunnable));
if (!runnable) { if (!runnable) {
return false; return false;
} }
if (NS_IsMainThread()) { if (NS_IsMainThread()) {
runnable->Run(); runnable->Run();
return true; return true;
} }
RefPtr<SyncRunnable> wrappedRunnable(new SyncRunnable(mDoneEvent,
runnable.forget())); SyncRunnable syncRunnable(runnable.forget());
// Make sure that wrappedRunnable remains valid while sitting in the APC queue
wrappedRunnable->AddRef();
if (!::QueueUserAPC(&MainThreadAPC, sMainThread, if (!::QueueUserAPC(&MainThreadAPC, sMainThread,
reinterpret_cast<UINT_PTR>(wrappedRunnable.get()))) { reinterpret_cast<UINT_PTR>(&syncRunnable))) {
// Enqueue failed so cancel the above AddRef
wrappedRunnable->Release();
return false; return false;
} }
// We should ensure a call to NtTestAlert() is made on the main thread so // 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 // 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 // omit this then the main thread will not check its APC queue until it is
// idle. // idle.
widget::WinUtils::SetAPCPending(); widget::WinUtils::SetAPCPending();
return WaitForCompletion(aTimeout);
return syncRunnable.WaitUntilComplete();
} }
/* static */ VOID CALLBACK /* static */ VOID CALLBACK
@ -125,8 +172,7 @@ MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam)
GeckoProfilerWakeRAII wakeProfiler; GeckoProfilerWakeRAII wakeProfiler;
mozilla::HangMonitor::NotifyActivity(mozilla::HangMonitor::kGeneralActivity); mozilla::HangMonitor::NotifyActivity(mozilla::HangMonitor::kGeneralActivity);
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
RefPtr<SyncRunnable> runnable(already_AddRefed<SyncRunnable>( auto runnable = reinterpret_cast<SyncRunnable*>(aParam);
reinterpret_cast<SyncRunnable*>(aParam)));
runnable->Run(); runnable->Run();
} }

View File

@ -21,21 +21,15 @@ class MainThreadInvoker
{ {
public: public:
MainThreadInvoker(); MainThreadInvoker();
~MainThreadInvoker();
bool WaitForCompletion(DWORD aTimeout = INFINITE); bool Invoke(already_AddRefed<nsIRunnable>&& aRunnable);
bool Invoke(already_AddRefed<nsIRunnable>&& aRunnable, static HANDLE GetTargetThread() { return sMainThread; }
DWORD aTimeout = INFINITE);
HANDLE GetTargetThread() const { return sMainThread; }
private: private:
static bool InitStatics(); static bool InitStatics();
static VOID CALLBACK MainThreadAPC(ULONG_PTR aParam); static VOID CALLBACK MainThreadAPC(ULONG_PTR aParam);
HANDLE mDoneEvent;
static HANDLE sMainThread; static HANDLE sMainThread;
static StaticRefPtr<nsIRunnable> sAlertRunnable;
}; };
} // namespace mscom } // namespace mscom

View File

@ -49,6 +49,7 @@ if CONFIG['ACCESSIBILITY']:
] ]
LOCAL_INCLUDES += [ LOCAL_INCLUDES += [
'/xpcom/base',
'/xpcom/build', '/xpcom/build',
] ]