/* -*- 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/BackgroundHangMonitor.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/mscom/SpinEvent.h" #include "mozilla/RefPtr.h" #include "mozilla/Unused.h" #include "private/prpriv.h" // For PR_GetThreadID #include // For NTSTATUS and NTAPI namespace { typedef NTSTATUS(NTAPI* NtTestAlertPtr)(VOID); /** * 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 SyncRunnable : public mozilla::Runnable { public: explicit SyncRunnable(already_AddRefed aRunnable) : mozilla::Runnable("MainThreadInvoker"), mRunnable(aRunnable) { static const bool gotStatics = InitStatics(); MOZ_ASSERT(gotStatics); Unused << gotStatics; } ~SyncRunnable() = default; NS_IMETHOD Run() override { if (mHasRun) { // The APC already ran, so we have nothing to do. return NS_OK; } // Run the pending APC in the queue. MOZ_ASSERT(sNtTestAlert); sNtTestAlert(); return NS_OK; } // This is called by MainThreadInvoker::MainThreadAPC. void APCRun() { mHasRun = true; TimeStamp runStart(TimeStamp::Now()); mRunnable->Run(); TimeStamp runEnd(TimeStamp::Now()); mDuration = runEnd - runStart; mEvent.Signal(); } bool WaitUntilComplete() { return mEvent.Wait(mozilla::mscom::MainThreadInvoker::GetTargetThread()); } const mozilla::TimeDuration& GetDuration() const { return mDuration; } private: bool mHasRun = false; nsCOMPtr mRunnable; mozilla::mscom::SpinEvent mEvent; mozilla::TimeDuration mDuration; static NtTestAlertPtr sNtTestAlert; static bool InitStatics() { sNtTestAlert = reinterpret_cast( ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert")); MOZ_ASSERT(sNtTestAlert); return sNtTestAlert; } }; NtTestAlertPtr SyncRunnable::sNtTestAlert = nullptr; } // anonymous namespace namespace mozilla { namespace mscom { HANDLE MainThreadInvoker::sMainThread = nullptr; /* static */ bool MainThreadInvoker::InitStatics() { nsCOMPtr 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); return !!sMainThread; } MainThreadInvoker::MainThreadInvoker() { static const bool gotStatics = InitStatics(); MOZ_ASSERT(gotStatics); Unused << gotStatics; } bool MainThreadInvoker::Invoke(already_AddRefed&& aRunnable) { nsCOMPtr runnable(std::move(aRunnable)); if (!runnable) { return false; } if (NS_IsMainThread()) { runnable->Run(); return true; } RefPtr syncRunnable = new SyncRunnable(runnable.forget()); // The main thread could be either blocked on a condition variable waiting // for a Gecko event, or it could be blocked waiting on a Windows HANDLE in // IPC code (doing a sync message send). In the former case, we wake it by // posting a Gecko runnable to the main thread. In the latter case, we wake // it using an APC. However, the latter case doesn't happen very often now // and APCs aren't otherwise run by the main thread. To ensure the // SyncRunnable is cleaned up, we need both to run consistently. // To do this, we: // 1. Queue an APC which does the actual work. // This ref gets released in MainThreadAPC when it runs. SyncRunnable* syncRunnableRef = syncRunnable.get(); NS_ADDREF(syncRunnableRef); if (!::QueueUserAPC(&MainThreadAPC, sMainThread, reinterpret_cast(syncRunnableRef))) { return false; } // 2. Post a Gecko runnable (which always runs). If the APC hasn't run, the // Gecko runnable runs it. Otherwise, it does nothing. if (NS_FAILED(SchedulerGroup::Dispatch(TaskCategory::Other, do_AddRef(syncRunnable)))) { return false; } bool result = syncRunnable->WaitUntilComplete(); mDuration = syncRunnable->GetDuration(); return result; } /* static */ VOID CALLBACK MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam) { AUTO_PROFILER_THREAD_WAKE; mozilla::BackgroundHangMonitor().NotifyActivity(); MOZ_ASSERT(NS_IsMainThread()); auto runnable = reinterpret_cast(aParam); runnable->APCRun(); NS_RELEASE(runnable); } } // namespace mscom } // namespace mozilla