diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /xpcom/threads/nsThreadManager.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/threads/nsThreadManager.cpp')
-rw-r--r-- | xpcom/threads/nsThreadManager.cpp | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp new file mode 100644 index 0000000000..367b520a54 --- /dev/null +++ b/xpcom/threads/nsThreadManager.cpp @@ -0,0 +1,798 @@ +/* -*- 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 "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsExceptionHandler.h" +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "nsExceptionHandler.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask +#include "mozilla/EventQueue.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/Mutex.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/ThreadEventQueue.h" +#include "mozilla/ThreadLocal.h" +#include "TaskController.h" +#include "ThreadEventTarget.h" +#ifdef MOZ_CANARY +# include <fcntl.h> +# include <unistd.h> +#endif + +#include "MainThreadIdlePeriod.h" + +using namespace mozilla; + +static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread; + +bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); } + +class BackgroundEventTarget final : public nsIEventTarget, + public TaskQueueTracker { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + BackgroundEventTarget() = default; + + nsresult Init(); + + already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue( + const char* aName); + + void BeginShutdown(nsTArray<RefPtr<ShutdownPromise>>&); + void FinishShutdown(); + + private: + ~BackgroundEventTarget() = default; + + nsCOMPtr<nsIThreadPool> mPool; + nsCOMPtr<nsIThreadPool> mIOPool; +}; + +NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget, TaskQueueTracker) + +nsresult BackgroundEventTarget::Init() { + nsCOMPtr<nsIThreadPool> pool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + nsresult rv = pool->SetName("BackgroundThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 2 makes deadlock during synchronous dispatch less likely. + rv = pool->SetThreadLimit(2); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = pool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + // Initialize the background I/O event target. + nsCOMPtr<nsIThreadPool> ioPool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + // The io pool spends a lot of its time blocking on io, so we want to offload + // these jobs on a lower priority if available. + rv = ioPool->SetQoSForThreads(nsIThread::QOS_PRIORITY_LOW); + NS_ENSURE_SUCCESS( + rv, rv); // note: currently infallible, keeping this for brevity. + + rv = ioPool->SetName("BgIOThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 4 makes deadlock during synchronous dispatch less likely. + rv = ioPool->SetThreadLimit(4); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ioPool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = ioPool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + pool.swap(mPool); + ioPool.swap(mIOPool); + + return NS_OK; +} + +NS_IMETHODIMP_(bool) +BackgroundEventTarget::IsOnCurrentThreadInfallible() { + return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread(); +} + +NS_IMETHODIMP +BackgroundEventTarget::IsOnCurrentThread(bool* aValue) { + bool value = false; + if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) { + *aValue = value; + return NS_OK; + } + return mIOPool->IsOnCurrentThread(aValue); +} + +NS_IMETHODIMP +BackgroundEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags) { + // We need to be careful here, because if an event is getting dispatched here + // from within TaskQueue::Runner::Run, it will be dispatched with + // NS_DISPATCH_AT_END, but we might not be running the event on the same + // pool, depending on which pool we were on and the dispatch flags. If we + // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool + // may not process the event in a timely fashion, which can lead to deadlock. + uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK; + bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK); + nsCOMPtr<nsIThreadPool>& pool = mayBlock ? mIOPool : mPool; + + // If we're already running on the pool we want to dispatch to, we can + // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin + // up a new thread. + // + // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues + // like those in the above comment. + if (pool->IsOnCurrentThread()) { + flags |= NS_DISPATCH_AT_END; + } else { + flags &= ~NS_DISPATCH_AT_END; + } + + return pool->Dispatch(std::move(aRunnable), flags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + return Dispatch(runnable.forget(), aFlags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t) { + nsCOMPtr<nsIRunnable> dropRunnable(aRunnable); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BackgroundEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BackgroundEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BackgroundEventTarget::BeginShutdown( + nsTArray<RefPtr<ShutdownPromise>>& promises) { + auto queues = GetAllTrackedTaskQueues(); + for (auto& queue : queues) { + promises.AppendElement(queue->BeginShutdown()); + } +} + +void BackgroundEventTarget::FinishShutdown() { + mPool->Shutdown(); + mIOPool->Shutdown(); +} + +already_AddRefed<nsISerialEventTarget> +BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) { + return TaskQueue::Create(do_AddRef(this), aName).forget(); +} + +extern "C" { +// This uses the C language linkage because it's exposed to Rust +// via the xpcom/rust/moz_task crate. +bool NS_IsMainThread() { return sTLSIsMainThread.get(); } +} + +void NS_SetMainThread() { + if (!sTLSIsMainThread.init()) { + MOZ_CRASH(); + } + sTLSIsMainThread.set(true); + MOZ_ASSERT(NS_IsMainThread()); + // We initialize the SerialEventTargetGuard's TLS here for simplicity as it + // needs to be initialized around the same time you would initialize + // sTLSIsMainThread. + SerialEventTargetGuard::InitTLS(); + nsThreadPool::InitTLS(); +} + +#ifdef DEBUG + +namespace mozilla { + +void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); } + +} // namespace mozilla + +#endif + +typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray; + +//----------------------------------------------------------------------------- + +/* static */ +void nsThreadManager::ReleaseThread(void* aData) { + static_cast<nsThread*>(aData)->Release(); +} + +// statically allocated instance +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::Release() { return 1; } +NS_IMPL_CLASSINFO(nsThreadManager, nullptr, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, + NS_THREADMANAGER_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) +NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) + +//----------------------------------------------------------------------------- + +/*static*/ nsThreadManager& nsThreadManager::get() { + static nsThreadManager sInstance; + return sInstance; +} + +nsThreadManager::nsThreadManager() + : mCurThreadIndex(0), mMainPRThread(nullptr), mInitialized(false) {} + +nsThreadManager::~nsThreadManager() = default; + +nsresult nsThreadManager::Init() { + // Child processes need to initialize the thread manager before they + // initialize XPCOM in order to set up the crash reporter. This leads to + // situations where we get initialized twice. + if (mInitialized) { + return NS_OK; + } + + if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseThread) == PR_FAILURE) { + return NS_ERROR_FAILURE; + } + +#ifdef MOZ_CANARY + const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + char* env_var_flag = getenv("MOZ_KILL_CANARIES"); + sCanaryOutputFD = + env_var_flag + ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO) + : 0; +#endif + + TaskController::Initialize(); + + // Initialize idle handling. + nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod(); + TaskController::Get()->SetIdleTaskManager( + new IdleTaskManager(idlePeriod.forget())); + + // Create main thread queue that forwards events to TaskController and + // construct main thread. + UniquePtr<EventQueue> queue = MakeUnique<EventQueue>(true); + + RefPtr<ThreadEventQueue> synchronizedQueue = + new ThreadEventQueue(std::move(queue), true); + + mMainThread = new nsThread(WrapNotNull(synchronizedQueue), + nsThread::MAIN_THREAD, {.stackSize = 0}); + + nsresult rv = mMainThread->InitCurrentThread(); + if (NS_FAILED(rv)) { + mMainThread = nullptr; + return rv; + } + + // We need to keep a pointer to the current thread, so we can satisfy + // GetIsMainThread calls that occur post-Shutdown. + mMainThread->GetPRThread(&mMainPRThread); + + // Init AbstractThread. + AbstractThread::InitTLS(); + AbstractThread::InitMainThread(); + + // Initialize the background event target. + RefPtr<BackgroundEventTarget> target(new BackgroundEventTarget()); + + rv = target->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mBackgroundEventTarget = std::move(target); + + mInitialized = true; + + return NS_OK; +} + +void nsThreadManager::ShutdownNonMainThreads() { + MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); + + // Prevent further access to the thread manager (no more new threads!) + // + // What happens if shutdown happens before NewThread completes? + // We Shutdown() the new thread, and return error if we've started Shutdown + // between when NewThread started, and when the thread finished initializing + // and registering with ThreadManager. + // + mInitialized = false; + + // Empty the main thread event queue before we begin shutting down threads. + NS_ProcessPendingEvents(mMainThread); + + mMainThread->mEvents->RunShutdownTasks(); + + nsTArray<RefPtr<ShutdownPromise>> promises; + mBackgroundEventTarget->BeginShutdown(promises); + + bool taskQueuesShutdown = false; + // It's fine to capture everything by reference in the Then handler since it + // runs before we exit the nested event loop, thanks to the SpinEventLoopUntil + // below. + ShutdownPromise::All(mMainThread, promises)->Then(mMainThread, __func__, [&] { + mBackgroundEventTarget->FinishShutdown(); + taskQueuesShutdown = true; + }); + + // Wait for task queues to shutdown, so we don't shut down the underlying + // threads of the background event target in the block below, thereby + // preventing the task queues from emptying, preventing the shutdown promises + // from resolving, and prevent anything checking `taskQueuesShutdown` from + // working. + mozilla::SpinEventLoopUntil( + "nsThreadManager::Shutdown"_ns, [&]() { return taskQueuesShutdown; }, + mMainThread); + + { + // We gather the threads into a list, so that we avoid holding the + // enumerator lock while calling nsIThread::Shutdown. + nsTArray<RefPtr<nsThread>> threadsToShutdown; + for (auto* thread : nsThread::Enumerate()) { + if (thread->ShutdownRequired()) { + threadsToShutdown.AppendElement(thread); + } + } + + // It's tempting to walk the list of threads here and tell them each to stop + // accepting new events, but that could lead to badness if one of those + // threads is stuck waiting for a response from another thread. To do it + // right, we'd need some way to interrupt the threads. + // + // Instead, we process events on the current thread while waiting for + // threads to shutdown. This means that we have to preserve a mostly + // functioning world until such time as the threads exit. + + // Shutdown all threads that require it (join with threads that we created). + for (auto& thread : threadsToShutdown) { + thread->Shutdown(); + } + } + + // NB: It's possible that there are events in the queue that want to *start* + // an asynchronous shutdown. But we have already shutdown the threads above, + // so there's no need to worry about them. We only have to wait for all + // in-flight asynchronous thread shutdowns to complete. + mMainThread->WaitForAllAsynchronousShutdowns(); + + // There are no more background threads at this point. +} + +void nsThreadManager::ShutdownMainThread() { + MOZ_ASSERT(!mInitialized, "Must have called BeginShutdown"); + + // Do NS_ProcessPendingEvents but with special handling to set + // mEventsAreDoomed atomically with the removal of the last event. This means + // that PutEvent cannot succeed if the event would be left in the main thread + // queue after our final call to NS_ProcessPendingEvents. + // See comments in `nsThread::ThreadFunc` for a more detailed explanation. + while (true) { + if (mMainThread->mEvents->ShutdownIfNoPendingEvents()) { + break; + } + NS_ProcessPendingEvents(mMainThread); + } + + // Normally thread shutdown clears the observer for the thread, but since the + // main thread is special we do it manually here after we're sure all events + // have been processed. + mMainThread->SetObserver(nullptr); + + mBackgroundEventTarget = nullptr; +} + +void nsThreadManager::ReleaseMainThread() { + MOZ_ASSERT(!mInitialized, "Must have called BeginShutdown"); + MOZ_ASSERT(!mBackgroundEventTarget, "Must have called ShutdownMainThread"); + MOZ_ASSERT(mMainThread); + + // Release main thread object. + mMainThread = nullptr; + + // Remove the TLS entry for the main thread. + PR_SetThreadPrivate(mCurThreadIndex, nullptr); +} + +void nsThreadManager::RegisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + aThread.AddRef(); // for TLS entry + PR_SetThreadPrivate(mCurThreadIndex, &aThread); +} + +void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + PR_SetThreadPrivate(mCurThreadIndex, nullptr); + // Ref-count balanced via ReleaseThread +} + +nsThread* nsThreadManager::CreateCurrentThread( + SynchronizedEventQueue* aQueue, nsThread::MainThreadFlag aMainThread) { + // Make sure we don't have an nsThread yet. + MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex)); + + if (!mInitialized) { + return nullptr; + } + + RefPtr<nsThread> thread = + new nsThread(WrapNotNull(aQueue), aMainThread, {.stackSize = 0}); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags) { + if (!mInitialized) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIEventTarget> backgroundTarget(mBackgroundEventTarget); + return backgroundTarget->Dispatch(aEvent, aDispatchFlags); +} + +already_AddRefed<nsISerialEventTarget> +nsThreadManager::CreateBackgroundTaskQueue(const char* aName) { + if (!mInitialized) { + return nullptr; + } + + return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName); +} + +nsThread* nsThreadManager::GetCurrentThread() { + // read thread local storage + void* data = PR_GetThreadPrivate(mCurThreadIndex); + if (data) { + return static_cast<nsThread*>(data); + } + + if (!mInitialized) { + return nullptr; + } + + // OK, that's fine. We'll dynamically create one :-) + // + // We assume that if we're implicitly creating a thread here that it doesn't + // want an event queue. Any thread which wants an event queue should + // explicitly create its nsThread wrapper. + RefPtr<nsThread> thread = new nsThread(); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +bool nsThreadManager::IsNSThread() const { + if (!mInitialized) { + return false; + } + if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) { + return thread->EventQueue(); + } + return false; +} + +NS_IMETHODIMP +nsThreadManager::NewNamedThread( + const nsACString& aName, nsIThreadManager::ThreadCreationOptions aOptions, + nsIThread** aResult) { + // Note: can be called from arbitrary threads + + // No new threads during Shutdown + if (NS_WARN_IF(!mInitialized)) { + return NS_ERROR_NOT_INITIALIZED; + } + + [[maybe_unused]] TimeStamp startTime = TimeStamp::Now(); + + RefPtr<ThreadEventQueue> queue = + new ThreadEventQueue(MakeUnique<EventQueue>()); + RefPtr<nsThread> thr = + new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aOptions); + nsresult rv = + thr->Init(aName); // Note: blocks until the new thread has been set up + if (NS_FAILED(rv)) { + return rv; + } + + // At this point, we expect that the thread has been registered in + // mThreadByPRThread; however, it is possible that it could have also been + // replaced by now, so we cannot really assert that it was added. Instead, + // kill it if we entered Shutdown() during/before Init() + + if (NS_WARN_IF(!mInitialized)) { + if (thr->ShutdownRequired()) { + thr->Shutdown(); // ok if it happens multiple times + } + return NS_ERROR_NOT_INITIALIZED; + } + + PROFILER_MARKER_TEXT( + "NewThread", OTHER, + MarkerOptions(MarkerStack::Capture(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + if (!NS_IsMainThread()) { + PROFILER_MARKER_TEXT( + "NewThread (non-main thread)", OTHER, + MarkerOptions(MarkerStack::Capture(), MarkerThreadId::MainThread(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + } + + thr.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + if (!NS_IsMainThread()) { + NS_WARNING( + "Called GetMainThread but there isn't a main thread and " + "we're not the main thread."); + } + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aResult = mMainThread); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetCurrentThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = GetCurrentThread(); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, + ShutdownPhase::NotInShutdown); +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilOrQuit( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, + ShutdownPhase::AppShutdownConfirmed); +} + +// statics from SpinEventLoopUntil.h +AutoNestedEventLoopAnnotation* AutoNestedEventLoopAnnotation::sCurrent = + nullptr; +StaticMutex AutoNestedEventLoopAnnotation::sStackMutex; + +// static from SpinEventLoopUntil.h +void AutoNestedEventLoopAnnotation::AnnotateXPCOMSpinEventLoopStack( + const nsACString& aStack) { + if (aStack.Length() > 0) { + nsCString prefixedStack(XRE_GetProcessTypeString()); + prefixedStack += ": "_ns + aStack; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::XPCOMSpinEventLoopStack, prefixedStack); + } else { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::XPCOMSpinEventLoopStack, ""_ns); + } +} + +nsresult nsThreadManager::SpinEventLoopUntilInternal( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition, + ShutdownPhase aShutdownPhaseToCheck) { + // XXX: We would want to AssertIsOnMainThread(); but that breaks some GTest. + nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition); + nsresult rv = NS_OK; + + if (!mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, [&]() -> bool { + // Check if an ongoing shutdown reached our limits. + if (aShutdownPhaseToCheck > ShutdownPhase::NotInShutdown && + AppShutdown::GetCurrentShutdownPhase() >= aShutdownPhaseToCheck) { + return true; + } + + bool isDone = false; + rv = condition->IsDone(&isDone); + // JS failure should be unusual, but we need to stop and propagate + // the error back to the caller. + if (NS_FAILED(rv)) { + return true; + } + + return isDone; + })) { + // We stopped early for some reason, which is unexpected. + return NS_ERROR_UNEXPECTED; + } + + // If we exited when the condition told us to, we need to return whether + // the condition encountered failure when executing. + return rv; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilEmpty() { + nsIThread* thread = NS_GetCurrentThread(); + + while (NS_HasPendingEvents(thread)) { + (void)NS_ProcessNextEvent(thread, false); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) { + nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget(); + target.forget(aTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority, + uint8_t aArgc) { + // Note: C++ callers should instead use NS_DispatchToMainThread. + MOZ_ASSERT(NS_IsMainThread()); + + // Keep this functioning during Shutdown + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + // If aPriority wasn't explicitly passed, that means it should be treated as + // PRIORITY_NORMAL. + if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) { + nsCOMPtr<nsIRunnable> event(aEvent); + return mMainThread->DispatchFromScript( + new PrioritizableRunnable(event.forget(), aPriority), 0); + } + return mMainThread->DispatchFromScript(aEvent, 0); +} + +class AutoMicroTaskWrapperRunnable final : public Runnable { + public: + explicit AutoMicroTaskWrapperRunnable(nsIRunnable* aEvent) + : Runnable("AutoMicroTaskWrapperRunnable"), mEvent(aEvent) { + MOZ_ASSERT(aEvent); + } + + private: + ~AutoMicroTaskWrapperRunnable() = default; + + NS_IMETHOD Run() override { + nsAutoMicroTask mt; + + return mEvent->Run(); + } + + RefPtr<nsIRunnable> mEvent; +}; + +NS_IMETHODIMP +nsThreadManager::DispatchToMainThreadWithMicroTask(nsIRunnable* aEvent, + uint32_t aPriority, + uint8_t aArgc) { + RefPtr<AutoMicroTaskWrapperRunnable> runnable = + new AutoMicroTaskWrapperRunnable(aEvent); + + return DispatchToMainThread(runnable, aPriority, aArgc); +} + +void nsThreadManager::EnableMainThreadEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->EnableInputEventPrioritization(); +} + +void nsThreadManager::FlushInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->FlushInputEventPrioritization(); +} + +void nsThreadManager::SuspendInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->SuspendInputEventPrioritization(); +} + +void nsThreadManager::ResumeInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->ResumeInputEventPrioritization(); +} + +// static +bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() { + MOZ_ASSERT(NS_IsMainThread()); + bool retVal = false; + if (get().mMainThread) { + get().mMainThread->HasPendingHighPriorityEvents(&retVal); + } + return retVal; +} + +NS_IMETHODIMP +nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent, + uint32_t aTimeout) { + // Note: C++ callers should instead use NS_DispatchToThreadQueue or + // NS_DispatchToCurrentThreadQueue. + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIRunnable> event(aEvent); + if (aTimeout) { + return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread, + EventQueuePriority::Idle); + } + + return NS_DispatchToThreadQueue(event.forget(), mMainThread, + EventQueuePriority::Idle); +} + +NS_IMETHODIMP +nsThreadManager::DispatchDirectTaskToCurrentThread(nsIRunnable* aEvent) { + NS_ENSURE_STATE(aEvent); + nsCOMPtr<nsIRunnable> runnable = aEvent; + return GetCurrentThread()->DispatchDirectTask(runnable.forget()); +} |