Bug 1755481: correct documentation of `nsIClipboard::getData`. r=mccr8
[gecko.git] / xpcom / threads / nsThreadManager.cpp
blobb7f78b15a0e74e82cd594b919d36a432070ab8e7
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsThreadManager.h"
8 #include "nsThread.h"
9 #include "nsThreadPool.h"
10 #include "nsThreadUtils.h"
11 #include "nsIClassInfoImpl.h"
12 #include "nsExceptionHandler.h"
13 #include "nsTArray.h"
14 #include "nsXULAppAPI.h"
15 #include "nsExceptionHandler.h"
16 #include "mozilla/AbstractThread.h"
17 #include "mozilla/AppShutdown.h"
18 #include "mozilla/ClearOnShutdown.h"
19 #include "mozilla/EventQueue.h"
20 #include "mozilla/InputTaskManager.h"
21 #include "mozilla/Mutex.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/ProfilerMarkers.h"
24 #include "mozilla/SpinEventLoopUntil.h"
25 #include "mozilla/StaticPtr.h"
26 #include "mozilla/TaskQueue.h"
27 #include "mozilla/ThreadEventQueue.h"
28 #include "mozilla/ThreadLocal.h"
29 #include "TaskController.h"
30 #include "ThreadEventTarget.h"
31 #ifdef MOZ_CANARY
32 # include <fcntl.h>
33 # include <unistd.h>
34 #endif
36 #include "MainThreadIdlePeriod.h"
37 #include "InputEventStatistics.h"
39 using namespace mozilla;
41 static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
43 bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); }
45 class BackgroundEventTarget final : public nsIEventTarget {
46 public:
47 NS_DECL_THREADSAFE_ISUPPORTS
48 NS_DECL_NSIEVENTTARGET_FULL
50 BackgroundEventTarget();
52 nsresult Init();
54 already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue(
55 const char* aName);
57 using CancelPromise = TaskQueue::CancelPromise::AllPromiseType;
58 RefPtr<CancelPromise> CancelBackgroundDelayedRunnables();
60 void BeginShutdown(nsTArray<RefPtr<ShutdownPromise>>&);
61 void FinishShutdown();
63 private:
64 ~BackgroundEventTarget() = default;
66 nsCOMPtr<nsIThreadPool> mPool;
67 nsCOMPtr<nsIThreadPool> mIOPool;
69 Mutex mMutex;
70 nsTArray<RefPtr<TaskQueue>> mTaskQueues;
71 bool mIsBackgroundDelayedRunnablesCanceled;
74 NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget)
76 BackgroundEventTarget::BackgroundEventTarget()
77 : mMutex("BackgroundEventTarget::mMutex") {}
79 nsresult BackgroundEventTarget::Init() {
80 nsCOMPtr<nsIThreadPool> pool(new nsThreadPool());
81 NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
83 nsresult rv = pool->SetName("BackgroundThreadPool"_ns);
84 NS_ENSURE_SUCCESS(rv, rv);
86 // Use potentially more conservative stack size.
87 rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
88 NS_ENSURE_SUCCESS(rv, rv);
90 // Thread limit of 2 makes deadlock during synchronous dispatch less likely.
91 rv = pool->SetThreadLimit(2);
92 NS_ENSURE_SUCCESS(rv, rv);
94 rv = pool->SetIdleThreadLimit(1);
95 NS_ENSURE_SUCCESS(rv, rv);
97 // Leave threads alive for up to 5 minutes
98 rv = pool->SetIdleThreadTimeout(300000);
99 NS_ENSURE_SUCCESS(rv, rv);
101 // Initialize the background I/O event target.
102 nsCOMPtr<nsIThreadPool> ioPool(new nsThreadPool());
103 NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
105 rv = ioPool->SetName("BgIOThreadPool"_ns);
106 NS_ENSURE_SUCCESS(rv, rv);
108 // Use potentially more conservative stack size.
109 rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
110 NS_ENSURE_SUCCESS(rv, rv);
112 // Thread limit of 4 makes deadlock during synchronous dispatch less likely.
113 rv = ioPool->SetThreadLimit(4);
114 NS_ENSURE_SUCCESS(rv, rv);
116 rv = ioPool->SetIdleThreadLimit(1);
117 NS_ENSURE_SUCCESS(rv, rv);
119 // Leave threads alive for up to 5 minutes
120 rv = ioPool->SetIdleThreadTimeout(300000);
121 NS_ENSURE_SUCCESS(rv, rv);
123 pool.swap(mPool);
124 ioPool.swap(mIOPool);
126 return NS_OK;
129 NS_IMETHODIMP_(bool)
130 BackgroundEventTarget::IsOnCurrentThreadInfallible() {
131 return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread();
134 NS_IMETHODIMP
135 BackgroundEventTarget::IsOnCurrentThread(bool* aValue) {
136 bool value = false;
137 if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) {
138 *aValue = value;
139 return NS_OK;
141 return mIOPool->IsOnCurrentThread(aValue);
144 NS_IMETHODIMP
145 BackgroundEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
146 uint32_t aFlags) {
147 // We need to be careful here, because if an event is getting dispatched here
148 // from within TaskQueue::Runner::Run, it will be dispatched with
149 // NS_DISPATCH_AT_END, but we might not be running the event on the same
150 // pool, depending on which pool we were on and the dispatch flags. If we
151 // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool
152 // may not process the event in a timely fashion, which can lead to deadlock.
153 uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK;
154 bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK);
155 nsCOMPtr<nsIThreadPool>& pool = mayBlock ? mIOPool : mPool;
157 // If we're already running on the pool we want to dispatch to, we can
158 // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin
159 // up a new thread.
161 // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues
162 // like those in the above comment.
163 if (pool->IsOnCurrentThread()) {
164 flags |= NS_DISPATCH_AT_END;
165 } else {
166 flags &= ~NS_DISPATCH_AT_END;
169 return pool->Dispatch(std::move(aRunnable), flags);
172 NS_IMETHODIMP
173 BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable,
174 uint32_t aFlags) {
175 nsCOMPtr<nsIRunnable> runnable(aRunnable);
176 return Dispatch(runnable.forget(), aFlags);
179 NS_IMETHODIMP
180 BackgroundEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aRunnable,
181 uint32_t) {
182 nsCOMPtr<nsIRunnable> dropRunnable(aRunnable);
183 return NS_ERROR_NOT_IMPLEMENTED;
186 void BackgroundEventTarget::BeginShutdown(
187 nsTArray<RefPtr<ShutdownPromise>>& promises) {
188 for (auto& queue : mTaskQueues) {
189 promises.AppendElement(queue->BeginShutdown());
193 void BackgroundEventTarget::FinishShutdown() {
194 mPool->Shutdown();
195 mIOPool->Shutdown();
198 already_AddRefed<nsISerialEventTarget>
199 BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) {
200 MutexAutoLock lock(mMutex);
202 RefPtr<TaskQueue> queue = new TaskQueue(do_AddRef(this), aName);
203 mTaskQueues.AppendElement(queue);
205 return queue.forget();
208 auto BackgroundEventTarget::CancelBackgroundDelayedRunnables()
209 -> RefPtr<CancelPromise> {
210 MOZ_ASSERT(NS_IsMainThread());
211 MutexAutoLock lock(mMutex);
212 mIsBackgroundDelayedRunnablesCanceled = true;
213 nsTArray<RefPtr<TaskQueue::CancelPromise>> promises;
214 for (const auto& tq : mTaskQueues) {
215 promises.AppendElement(tq->CancelDelayedRunnables());
217 return TaskQueue::CancelPromise::All(GetMainThreadSerialEventTarget(),
218 promises);
221 extern "C" {
222 // This uses the C language linkage because it's exposed to Rust
223 // via the xpcom/rust/moz_task crate.
224 bool NS_IsMainThread() { return sTLSIsMainThread.get(); }
227 void NS_SetMainThread() {
228 if (!sTLSIsMainThread.init()) {
229 MOZ_CRASH();
231 sTLSIsMainThread.set(true);
232 MOZ_ASSERT(NS_IsMainThread());
233 // We initialize the SerialEventTargetGuard's TLS here for simplicity as it
234 // needs to be initialized around the same time you would initialize
235 // sTLSIsMainThread.
236 SerialEventTargetGuard::InitTLS();
239 #ifdef DEBUG
241 namespace mozilla {
243 void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); }
245 } // namespace mozilla
247 #endif
249 typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray;
251 static Atomic<bool> sShutdownComplete;
253 //-----------------------------------------------------------------------------
255 /* static */
256 void nsThreadManager::ReleaseThread(void* aData) {
257 if (sShutdownComplete) {
258 // We've already completed shutdown and released the references to all or
259 // our TLS wrappers. Don't try to release them again.
260 return;
263 auto* thread = static_cast<nsThread*>(aData);
265 if (thread->mHasTLSEntry) {
266 thread->mHasTLSEntry = false;
267 thread->Release();
271 // statically allocated instance
272 NS_IMETHODIMP_(MozExternalRefCountType)
273 nsThreadManager::AddRef() { return 2; }
274 NS_IMETHODIMP_(MozExternalRefCountType)
275 nsThreadManager::Release() { return 1; }
276 NS_IMPL_CLASSINFO(nsThreadManager, nullptr,
277 nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
278 NS_THREADMANAGER_CID)
279 NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager)
280 NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager)
282 //-----------------------------------------------------------------------------
284 /*static*/ nsThreadManager& nsThreadManager::get() {
285 static nsThreadManager sInstance;
286 return sInstance;
289 nsThreadManager::nsThreadManager()
290 : mCurThreadIndex(0), mMainPRThread(nullptr), mInitialized(false) {}
292 nsThreadManager::~nsThreadManager() = default;
294 nsresult nsThreadManager::Init() {
295 // Child processes need to initialize the thread manager before they
296 // initialize XPCOM in order to set up the crash reporter. This leads to
297 // situations where we get initialized twice.
298 if (mInitialized) {
299 return NS_OK;
302 if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseThread) == PR_FAILURE) {
303 return NS_ERROR_FAILURE;
306 #ifdef MOZ_CANARY
307 const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
308 const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
309 char* env_var_flag = getenv("MOZ_KILL_CANARIES");
310 sCanaryOutputFD =
311 env_var_flag
312 ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO)
313 : 0;
314 #endif
316 TaskController::Initialize();
318 // Initialize idle handling.
319 nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
320 TaskController::Get()->SetIdleTaskManager(
321 new IdleTaskManager(idlePeriod.forget()));
323 // Create main thread queue that forwards events to TaskController and
324 // construct main thread.
325 UniquePtr<EventQueue> queue = MakeUnique<EventQueue>(true);
327 RefPtr<ThreadEventQueue> synchronizedQueue =
328 new ThreadEventQueue(std::move(queue), true);
330 mMainThread =
331 new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, 0);
333 nsresult rv = mMainThread->InitCurrentThread();
334 if (NS_FAILED(rv)) {
335 mMainThread = nullptr;
336 return rv;
339 // We need to keep a pointer to the current thread, so we can satisfy
340 // GetIsMainThread calls that occur post-Shutdown.
341 mMainThread->GetPRThread(&mMainPRThread);
343 // Init AbstractThread.
344 AbstractThread::InitTLS();
345 AbstractThread::InitMainThread();
347 // Initialize the background event target.
348 RefPtr<BackgroundEventTarget> target(new BackgroundEventTarget());
350 rv = target->Init();
351 NS_ENSURE_SUCCESS(rv, rv);
353 mBackgroundEventTarget = std::move(target);
355 mInitialized = true;
357 return NS_OK;
360 void nsThreadManager::Shutdown() {
361 MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread");
363 // Prevent further access to the thread manager (no more new threads!)
365 // What happens if shutdown happens before NewThread completes?
366 // We Shutdown() the new thread, and return error if we've started Shutdown
367 // between when NewThread started, and when the thread finished initializing
368 // and registering with ThreadManager.
370 mInitialized = false;
372 // Empty the main thread event queue before we begin shutting down threads.
373 NS_ProcessPendingEvents(mMainThread);
375 nsTArray<RefPtr<ShutdownPromise>> promises;
376 mBackgroundEventTarget->BeginShutdown(promises);
378 bool taskQueuesShutdown = false;
379 // It's fine to capture everything by reference in the Then handler since it
380 // runs before we exit the nested event loop, thanks to the SpinEventLoopUntil
381 // below.
382 ShutdownPromise::All(mMainThread, promises)->Then(mMainThread, __func__, [&] {
383 mBackgroundEventTarget->FinishShutdown();
384 taskQueuesShutdown = true;
387 // Wait for task queues to shutdown, so we don't shut down the underlying
388 // threads of the background event target in the block below, thereby
389 // preventing the task queues from emptying, preventing the shutdown promises
390 // from resolving, and prevent anything checking `taskQueuesShutdown` from
391 // working.
392 mozilla::SpinEventLoopUntil(
393 "nsThreadManager::Shutdown"_ns, [&]() { return taskQueuesShutdown; },
394 mMainThread);
397 // We gather the threads from the hashtable into a list, so that we avoid
398 // holding the enumerator lock while calling nsIThread::Shutdown.
399 nsTArray<RefPtr<nsThread>> threadsToShutdown;
400 for (auto* thread : nsThread::Enumerate()) {
401 if (thread->ShutdownRequired()) {
402 threadsToShutdown.AppendElement(thread);
406 // It's tempting to walk the list of threads here and tell them each to stop
407 // accepting new events, but that could lead to badness if one of those
408 // threads is stuck waiting for a response from another thread. To do it
409 // right, we'd need some way to interrupt the threads.
411 // Instead, we process events on the current thread while waiting for
412 // threads to shutdown. This means that we have to preserve a mostly
413 // functioning world until such time as the threads exit.
415 // Shutdown all threads that require it (join with threads that we created).
416 for (auto& thread : threadsToShutdown) {
417 thread->Shutdown();
421 // NB: It's possible that there are events in the queue that want to *start*
422 // an asynchronous shutdown. But we have already shutdown the threads above,
423 // so there's no need to worry about them. We only have to wait for all
424 // in-flight asynchronous thread shutdowns to complete.
425 mMainThread->WaitForAllAsynchronousShutdowns();
427 mMainThread->mEventTarget->NotifyShutdown();
429 // In case there are any more events somehow...
430 NS_ProcessPendingEvents(mMainThread);
432 // There are no more background threads at this point.
434 // Normally thread shutdown clears the observer for the thread, but since the
435 // main thread is special we do it manually here after we're sure all events
436 // have been processed.
437 mMainThread->SetObserver(nullptr);
439 mBackgroundEventTarget = nullptr;
441 // Release main thread object.
442 mMainThread = nullptr;
444 // Remove the TLS entry for the main thread.
445 PR_SetThreadPrivate(mCurThreadIndex, nullptr);
448 // Cleanup the last references to any threads which haven't shut down yet.
449 nsTArray<RefPtr<nsThread>> threads;
450 for (auto* thread : nsThread::Enumerate()) {
451 if (thread->mHasTLSEntry) {
452 threads.AppendElement(dont_AddRef(thread));
453 thread->mHasTLSEntry = false;
458 // xpcshell tests sometimes leak the main thread. They don't enable leak
459 // checking, so that doesn't cause the test to fail, but leaving the entry in
460 // the thread list triggers an assertion, which does.
461 nsThread::ClearThreadList();
463 sShutdownComplete = true;
466 void nsThreadManager::RegisterCurrentThread(nsThread& aThread) {
467 MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
469 aThread.AddRef(); // for TLS entry
470 aThread.mHasTLSEntry = true;
471 PR_SetThreadPrivate(mCurThreadIndex, &aThread);
474 void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) {
475 MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
477 PR_SetThreadPrivate(mCurThreadIndex, nullptr);
478 // Ref-count balanced via ReleaseThread
481 nsThread* nsThreadManager::CreateCurrentThread(
482 SynchronizedEventQueue* aQueue, nsThread::MainThreadFlag aMainThread) {
483 // Make sure we don't have an nsThread yet.
484 MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex));
486 if (!mInitialized) {
487 return nullptr;
490 RefPtr<nsThread> thread = new nsThread(WrapNotNull(aQueue), aMainThread, 0);
491 if (!thread || NS_FAILED(thread->InitCurrentThread())) {
492 return nullptr;
495 return thread.get(); // reference held in TLS
498 nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent,
499 uint32_t aDispatchFlags) {
500 if (!mInitialized) {
501 return NS_ERROR_FAILURE;
504 nsCOMPtr<nsIEventTarget> backgroundTarget(mBackgroundEventTarget);
505 return backgroundTarget->Dispatch(aEvent, aDispatchFlags);
508 already_AddRefed<nsISerialEventTarget>
509 nsThreadManager::CreateBackgroundTaskQueue(const char* aName) {
510 if (!mInitialized) {
511 return nullptr;
514 return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName);
517 void nsThreadManager::CancelBackgroundDelayedRunnables() {
518 if (!mInitialized) {
519 return;
522 bool canceled = false;
523 mBackgroundEventTarget->CancelBackgroundDelayedRunnables()->Then(
524 GetMainThreadSerialEventTarget(), __func__, [&] { canceled = true; });
525 mozilla::SpinEventLoopUntil(
526 "nsThreadManager::CancelBackgroundDelayedRunnables"_ns,
527 [&]() { return canceled; });
530 nsThread* nsThreadManager::GetCurrentThread() {
531 // read thread local storage
532 void* data = PR_GetThreadPrivate(mCurThreadIndex);
533 if (data) {
534 return static_cast<nsThread*>(data);
537 if (!mInitialized) {
538 return nullptr;
541 // OK, that's fine. We'll dynamically create one :-)
543 // We assume that if we're implicitly creating a thread here that it doesn't
544 // want an event queue. Any thread which wants an event queue should
545 // explicitly create its nsThread wrapper.
546 RefPtr<nsThread> thread = new nsThread();
547 if (!thread || NS_FAILED(thread->InitCurrentThread())) {
548 return nullptr;
551 return thread.get(); // reference held in TLS
554 bool nsThreadManager::IsNSThread() const {
555 if (!mInitialized) {
556 return false;
558 if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) {
559 return thread->EventQueue();
561 return false;
564 NS_IMETHODIMP
565 nsThreadManager::NewNamedThread(const nsACString& aName, uint32_t aStackSize,
566 nsIThread** aResult) {
567 // Note: can be called from arbitrary threads
569 // No new threads during Shutdown
570 if (NS_WARN_IF(!mInitialized)) {
571 return NS_ERROR_NOT_INITIALIZED;
574 [[maybe_unused]] TimeStamp startTime = TimeStamp::Now();
576 RefPtr<ThreadEventQueue> queue =
577 new ThreadEventQueue(MakeUnique<EventQueue>());
578 RefPtr<nsThread> thr =
579 new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aStackSize);
580 nsresult rv =
581 thr->Init(aName); // Note: blocks until the new thread has been set up
582 if (NS_FAILED(rv)) {
583 return rv;
586 // At this point, we expect that the thread has been registered in
587 // mThreadByPRThread; however, it is possible that it could have also been
588 // replaced by now, so we cannot really assert that it was added. Instead,
589 // kill it if we entered Shutdown() during/before Init()
591 if (NS_WARN_IF(!mInitialized)) {
592 if (thr->ShutdownRequired()) {
593 thr->Shutdown(); // ok if it happens multiple times
595 return NS_ERROR_NOT_INITIALIZED;
598 PROFILER_MARKER_TEXT(
599 "NewThread", OTHER,
600 MarkerOptions(MarkerStack::Capture(),
601 MarkerTiming::IntervalUntilNowFrom(startTime)),
602 aName);
603 if (!NS_IsMainThread()) {
604 PROFILER_MARKER_TEXT(
605 "NewThread (non-main thread)", OTHER,
606 MarkerOptions(MarkerStack::Capture(), MarkerThreadId::MainThread(),
607 MarkerTiming::IntervalUntilNowFrom(startTime)),
608 aName);
611 thr.forget(aResult);
612 return NS_OK;
615 NS_IMETHODIMP
616 nsThreadManager::GetMainThread(nsIThread** aResult) {
617 // Keep this functioning during Shutdown
618 if (!mMainThread) {
619 if (!NS_IsMainThread()) {
620 NS_WARNING(
621 "Called GetMainThread but there isn't a main thread and "
622 "we're not the main thread.");
624 return NS_ERROR_NOT_INITIALIZED;
626 NS_ADDREF(*aResult = mMainThread);
627 return NS_OK;
630 NS_IMETHODIMP
631 nsThreadManager::GetCurrentThread(nsIThread** aResult) {
632 // Keep this functioning during Shutdown
633 if (!mMainThread) {
634 return NS_ERROR_NOT_INITIALIZED;
636 *aResult = GetCurrentThread();
637 if (!*aResult) {
638 return NS_ERROR_OUT_OF_MEMORY;
640 NS_ADDREF(*aResult);
641 return NS_OK;
644 NS_IMETHODIMP
645 nsThreadManager::SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis,
646 nsINestedEventLoopCondition* aCondition) {
647 return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition,
648 ShutdownPhase::NotInShutdown);
651 NS_IMETHODIMP
652 nsThreadManager::SpinEventLoopUntilOrQuit(
653 const nsACString& aVeryGoodReasonToDoThis,
654 nsINestedEventLoopCondition* aCondition) {
655 return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition,
656 ShutdownPhase::AppShutdownConfirmed);
659 // statics from SpinEventLoopUntil.h
660 AutoNestedEventLoopAnnotation* AutoNestedEventLoopAnnotation::sCurrent =
661 nullptr;
662 StaticMutex AutoNestedEventLoopAnnotation::sStackMutex;
664 // static from SpinEventLoopUntil.h
665 void AutoNestedEventLoopAnnotation::AnnotateXPCOMSpinEventLoopStack(
666 const nsACString& aStack) {
667 if (aStack.Length() > 0) {
668 nsCString prefixedStack(XRE_GetProcessTypeString());
669 prefixedStack += ": "_ns + aStack;
670 CrashReporter::AnnotateCrashReport(
671 CrashReporter::Annotation::XPCOMSpinEventLoopStack, prefixedStack);
672 } else {
673 CrashReporter::AnnotateCrashReport(
674 CrashReporter::Annotation::XPCOMSpinEventLoopStack, ""_ns);
678 nsresult nsThreadManager::SpinEventLoopUntilInternal(
679 const nsACString& aVeryGoodReasonToDoThis,
680 nsINestedEventLoopCondition* aCondition,
681 ShutdownPhase aShutdownPhaseToCheck) {
682 // XXX: We would want to AssertIsOnMainThread(); but that breaks some GTest.
683 nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition);
684 nsresult rv = NS_OK;
686 if (!mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, [&]() -> bool {
687 // Check if an ongoing shutdown reached our limits.
688 if (aShutdownPhaseToCheck > ShutdownPhase::NotInShutdown &&
689 AppShutdown::GetCurrentShutdownPhase() >= aShutdownPhaseToCheck) {
690 return true;
693 bool isDone = false;
694 rv = condition->IsDone(&isDone);
695 // JS failure should be unusual, but we need to stop and propagate
696 // the error back to the caller.
697 if (NS_FAILED(rv)) {
698 return true;
701 return isDone;
702 })) {
703 // We stopped early for some reason, which is unexpected.
704 return NS_ERROR_UNEXPECTED;
707 // If we exited when the condition told us to, we need to return whether
708 // the condition encountered failure when executing.
709 return rv;
712 NS_IMETHODIMP
713 nsThreadManager::SpinEventLoopUntilEmpty() {
714 nsIThread* thread = NS_GetCurrentThread();
716 while (NS_HasPendingEvents(thread)) {
717 (void)NS_ProcessNextEvent(thread, false);
720 return NS_OK;
723 NS_IMETHODIMP
724 nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) {
725 nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget();
726 target.forget(aTarget);
727 return NS_OK;
730 NS_IMETHODIMP
731 nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority,
732 uint8_t aArgc) {
733 // Note: C++ callers should instead use NS_DispatchToMainThread.
734 MOZ_ASSERT(NS_IsMainThread());
736 // Keep this functioning during Shutdown
737 if (NS_WARN_IF(!mMainThread)) {
738 return NS_ERROR_NOT_INITIALIZED;
740 // If aPriority wasn't explicitly passed, that means it should be treated as
741 // PRIORITY_NORMAL.
742 if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) {
743 nsCOMPtr<nsIRunnable> event(aEvent);
744 return mMainThread->DispatchFromScript(
745 new PrioritizableRunnable(event.forget(), aPriority), 0);
747 return mMainThread->DispatchFromScript(aEvent, 0);
750 void nsThreadManager::EnableMainThreadEventPrioritization() {
751 MOZ_ASSERT(NS_IsMainThread());
752 InputEventStatistics::Get().SetEnable(true);
753 InputTaskManager::Get()->EnableInputEventPrioritization();
756 void nsThreadManager::FlushInputEventPrioritization() {
757 MOZ_ASSERT(NS_IsMainThread());
758 InputTaskManager::Get()->FlushInputEventPrioritization();
761 void nsThreadManager::SuspendInputEventPrioritization() {
762 MOZ_ASSERT(NS_IsMainThread());
763 InputTaskManager::Get()->SuspendInputEventPrioritization();
766 void nsThreadManager::ResumeInputEventPrioritization() {
767 MOZ_ASSERT(NS_IsMainThread());
768 InputTaskManager::Get()->ResumeInputEventPrioritization();
771 // static
772 bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() {
773 MOZ_ASSERT(NS_IsMainThread());
774 bool retVal = false;
775 if (get().mMainThread) {
776 get().mMainThread->HasPendingHighPriorityEvents(&retVal);
778 return retVal;
781 NS_IMETHODIMP
782 nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent,
783 uint32_t aTimeout) {
784 // Note: C++ callers should instead use NS_DispatchToThreadQueue or
785 // NS_DispatchToCurrentThreadQueue.
786 MOZ_ASSERT(NS_IsMainThread());
788 nsCOMPtr<nsIRunnable> event(aEvent);
789 if (aTimeout) {
790 return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread,
791 EventQueuePriority::Idle);
794 return NS_DispatchToThreadQueue(event.forget(), mMainThread,
795 EventQueuePriority::Idle);