mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 07:13:20 +00:00
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:
parent
78c3624c28
commit
8cfa990adf
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -49,6 +49,7 @@ if CONFIG['ACCESSIBILITY']:
|
|||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_INCLUDES += [
|
LOCAL_INCLUDES += [
|
||||||
|
'/xpcom/base',
|
||||||
'/xpcom/build',
|
'/xpcom/build',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user