/* -*- 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 "MainThreadUtils.h" #include "mozilla/Assertions.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/DebugOnly.h" #include "mozilla/RefPtr.h" #include "private/prpriv.h" // For PR_GetThreadID #include // For NTSTATUS and NTAPI namespace { class SyncRunnable : public mozilla::Runnable { public: SyncRunnable(HANDLE aEvent, already_AddRefed&& aRunnable) : mDoneEvent(aEvent) , mRunnable(aRunnable) { MOZ_ASSERT(aEvent); MOZ_ASSERT(mRunnable); } NS_IMETHOD Run() override { mRunnable->Run(); ::SetEvent(mDoneEvent); return NS_OK; } private: HANDLE mDoneEvent; nsCOMPtr mRunnable; }; typedef NTSTATUS (NTAPI* NtTestAlertPtr)(VOID); } // anonymous namespace namespace mozilla { namespace mscom { HANDLE MainThreadInvoker::sMainThread = nullptr; StaticRefPtr MainThreadInvoker::sAlertRunnable; /* 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); if (!sMainThread) { return false; } NtTestAlertPtr NtTestAlert = reinterpret_cast( ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert")); sAlertRunnable = ::NS_NewRunnableFunction([NtTestAlert]() -> void { // We're using NtTestAlert() instead of SleepEx() so that the main thread // never gives up its quantum if there are no APCs pending. NtTestAlert(); }).take(); if (sAlertRunnable) { ClearOnShutdown(&sAlertRunnable); } return !!sAlertRunnable; } MainThreadInvoker::MainThreadInvoker() : mDoneEvent(::CreateEvent(nullptr, FALSE, FALSE, nullptr)) { static const bool gotStatics = InitStatics(); MOZ_ASSERT(gotStatics); } MainThreadInvoker::~MainThreadInvoker() { if (mDoneEvent) { ::CloseHandle(mDoneEvent); } } bool MainThreadInvoker::WaitForCompletion(DWORD aTimeout) { HANDLE handles[] = {mDoneEvent, sMainThread}; DWORD waitResult = ::WaitForMultipleObjects(ArrayLength(handles), handles, FALSE, aTimeout); return waitResult == WAIT_OBJECT_0; } bool MainThreadInvoker::Invoke(already_AddRefed&& aRunnable, DWORD aTimeout) { nsCOMPtr runnable(Move(aRunnable)); if (!runnable) { return false; } if (NS_IsMainThread()) { runnable->Run(); return true; } RefPtr wrappedRunnable(new SyncRunnable(mDoneEvent, runnable.forget())); // Make sure that wrappedRunnable remains valid while sitting in the APC queue wrappedRunnable->AddRef(); if (!::QueueUserAPC(&MainThreadAPC, sMainThread, reinterpret_cast(wrappedRunnable.get()))) { // Enqueue failed so cancel the above AddRef wrappedRunnable->Release(); return false; } // We should enqueue a call to NtTestAlert() 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. Note that failing to // dispatch this event is non-fatal, but it will delay execution of the APC. NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(sAlertRunnable))); return WaitForCompletion(aTimeout); } /* static */ VOID CALLBACK MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam) { MOZ_ASSERT(NS_IsMainThread()); RefPtr runnable(already_AddRefed( reinterpret_cast(aParam))); runnable->Run(); } } // namespace mscom } // namespace mozilla