/* -*- 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 "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 { /** * 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: explicit SyncRunnable(already_AddRefed<nsIRunnable>&& aRunnable) : mDoneEvent(sIsMulticore ? nullptr : ::CreateEventW(nullptr, FALSE, FALSE, nullptr)) , mDone(false) , mRunnable(aRunnable) { MOZ_ASSERT(sIsMulticore || mDoneEvent); MOZ_ASSERT(mRunnable); } ~SyncRunnable() { if (mDoneEvent) { ::CloseHandle(mDoneEvent); } } void Run() { mRunnable->Run(); 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 { HANDLE MainThreadInvoker::sMainThread = nullptr; /* 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() { static const bool gotStatics = InitStatics(); MOZ_ASSERT(gotStatics); } bool MainThreadInvoker::Invoke(already_AddRefed<nsIRunnable>&& aRunnable) { nsCOMPtr<nsIRunnable> runnable(Move(aRunnable)); if (!runnable) { return false; } if (NS_IsMainThread()) { runnable->Run(); return true; } SyncRunnable syncRunnable(runnable.forget()); if (!::QueueUserAPC(&MainThreadAPC, sMainThread, 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 syncRunnable.WaitUntilComplete(); } /* static */ VOID CALLBACK MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam) { GeckoProfilerWakeRAII wakeProfiler; mozilla::HangMonitor::NotifyActivity(mozilla::HangMonitor::kGeneralActivity); MOZ_ASSERT(NS_IsMainThread()); auto runnable = reinterpret_cast<SyncRunnable*>(aParam); runnable->Run(); } } // namespace mscom } // namespace mozilla