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/. */
6 
7 #include "nsThreadManager.h"
8 #include "nsThread.h"
9 #include "nsThreadPool.h"
10 #include "nsThreadUtils.h"
11 #include "nsIClassInfoImpl.h"
12 #include "nsTArray.h"
13 #include "nsXULAppAPI.h"
14 #include "MainThreadQueue.h"
15 #include "mozilla/AbstractThread.h"
16 #include "mozilla/ClearOnShutdown.h"
17 #include "mozilla/EventQueue.h"
18 #include "mozilla/Mutex.h"
19 #include "mozilla/Preferences.h"
20 #include "mozilla/StaticPtr.h"
21 #include "mozilla/TaskQueue.h"
22 #include "mozilla/ThreadEventQueue.h"
23 #include "mozilla/ThreadLocal.h"
24 #include "PrioritizedEventQueue.h"
25 #ifdef MOZ_CANARY
26 #  include <fcntl.h>
27 #  include <unistd.h>
28 #endif
29 
30 #include "MainThreadIdlePeriod.h"
31 #include "InputEventStatistics.h"
32 
33 using namespace mozilla;
34 
35 static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
36 
NS_IsMainThreadTLSInitialized()37 bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); }
38 
39 class BackgroundEventTarget final : public nsIEventTarget {
40  public:
41   NS_DECL_THREADSAFE_ISUPPORTS
42   NS_DECL_NSIEVENTTARGET_FULL
43 
44   BackgroundEventTarget();
45 
46   nsresult Init();
47 
48   already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue(
49       const char* aName);
50 
51   void BeginShutdown(nsTArray<RefPtr<ShutdownPromise>>&);
52   void FinishShutdown();
53 
54  private:
55   ~BackgroundEventTarget() = default;
56 
57   nsCOMPtr<nsIThreadPool> mPool;
58   nsCOMPtr<nsIThreadPool> mIOPool;
59 
60   Mutex mMutex;
61   nsTArray<RefPtr<TaskQueue>> mTaskQueues;
62 };
63 
NS_IMPL_ISUPPORTS(BackgroundEventTarget,nsIEventTarget)64 NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget)
65 
66 BackgroundEventTarget::BackgroundEventTarget()
67     : mMutex("BackgroundEventTarget::mMutex") {}
68 
Init()69 nsresult BackgroundEventTarget::Init() {
70   nsCOMPtr<nsIThreadPool> pool(new nsThreadPool());
71   NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
72 
73   nsresult rv = pool->SetName(NS_LITERAL_CSTRING("BackgroundThreadPool"));
74   NS_ENSURE_SUCCESS(rv, rv);
75 
76   // Use potentially more conservative stack size.
77   rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
78   NS_ENSURE_SUCCESS(rv, rv);
79 
80   // For now just one thread. Can increase easily later if we want.
81   rv = pool->SetThreadLimit(1);
82   NS_ENSURE_SUCCESS(rv, rv);
83 
84   // Leave threads alive for up to 5 minutes
85   rv = pool->SetIdleThreadTimeout(300000);
86   NS_ENSURE_SUCCESS(rv, rv);
87 
88   // Initialize the background I/O event target.
89   nsCOMPtr<nsIThreadPool> ioPool(new nsThreadPool());
90   NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
91 
92   rv = ioPool->SetName(NS_LITERAL_CSTRING("BgIOThreadPool"));
93   NS_ENSURE_SUCCESS(rv, rv);
94 
95   // Use potentially more conservative stack size.
96   rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
97   NS_ENSURE_SUCCESS(rv, rv);
98 
99   // For now just one thread. Can increase easily later if we want.
100   rv = ioPool->SetThreadLimit(1);
101   NS_ENSURE_SUCCESS(rv, rv);
102 
103   // Leave threads alive for up to 5 minutes
104   rv = ioPool->SetIdleThreadTimeout(300000);
105   NS_ENSURE_SUCCESS(rv, rv);
106 
107   pool.swap(mPool);
108   ioPool.swap(mIOPool);
109 
110   return NS_OK;
111 }
112 
NS_IMETHODIMP_(bool)113 NS_IMETHODIMP_(bool)
114 BackgroundEventTarget::IsOnCurrentThreadInfallible() {
115   return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread();
116 }
117 
118 NS_IMETHODIMP
IsOnCurrentThread(bool * aValue)119 BackgroundEventTarget::IsOnCurrentThread(bool* aValue) {
120   bool value = false;
121   if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) {
122     *aValue = value;
123     return NS_OK;
124   }
125   return mIOPool->IsOnCurrentThread(aValue);
126 }
127 
128 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aRunnable,uint32_t aFlags)129 BackgroundEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
130                                 uint32_t aFlags) {
131   // We need to be careful here, because if an event is getting dispatched here
132   // from within TaskQueue::Runner::Run, it will be dispatched with
133   // NS_DISPATCH_AT_END, but we might not be running the event on the same
134   // pool, depending on which pool we were on and the dispatch flags.  If we
135   // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool
136   // may not process the event in a timely fashion, which can lead to deadlock.
137   uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK;
138   bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK);
139   nsCOMPtr<nsIThreadPool>& pool = mayBlock ? mIOPool : mPool;
140 
141   // If we're already running on the pool we want to dispatch to, we can
142   // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin
143   // up a new thread.
144   //
145   // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues
146   // like those in the above comment.
147   if (pool->IsOnCurrentThread()) {
148     flags |= NS_DISPATCH_AT_END;
149   } else {
150     flags &= ~NS_DISPATCH_AT_END;
151   }
152 
153   return pool->Dispatch(std::move(aRunnable), flags);
154 }
155 
156 NS_IMETHODIMP
DispatchFromScript(nsIRunnable * aRunnable,uint32_t aFlags)157 BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable,
158                                           uint32_t aFlags) {
159   nsCOMPtr<nsIRunnable> runnable(aRunnable);
160   return Dispatch(runnable.forget(), aFlags);
161 }
162 
163 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable> aRunnable,uint32_t)164 BackgroundEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aRunnable,
165                                        uint32_t) {
166   nsCOMPtr<nsIRunnable> dropRunnable(aRunnable);
167   return NS_ERROR_NOT_IMPLEMENTED;
168 }
169 
BeginShutdown(nsTArray<RefPtr<ShutdownPromise>> & promises)170 void BackgroundEventTarget::BeginShutdown(
171     nsTArray<RefPtr<ShutdownPromise>>& promises) {
172   for (auto& queue : mTaskQueues) {
173     promises.AppendElement(queue->BeginShutdown());
174   }
175 }
176 
FinishShutdown()177 void BackgroundEventTarget::FinishShutdown() {
178   mPool->Shutdown();
179   mIOPool->Shutdown();
180 }
181 
182 already_AddRefed<nsISerialEventTarget>
CreateBackgroundTaskQueue(const char * aName)183 BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) {
184   MutexAutoLock lock(mMutex);
185 
186   RefPtr<TaskQueue> queue = new TaskQueue(do_AddRef(this), aName,
187                                           /*aSupportsTailDispatch=*/false,
188                                           /*aRetainFlags=*/true);
189   nsCOMPtr<nsISerialEventTarget> target(queue->WrapAsEventTarget());
190 
191   mTaskQueues.AppendElement(queue.forget());
192 
193   return target.forget();
194 }
195 
196 extern "C" {
197 // This uses the C language linkage because it's exposed to Rust
198 // via the xpcom/rust/moz_task crate.
NS_IsMainThread()199 bool NS_IsMainThread() { return sTLSIsMainThread.get(); }
200 }
201 
NS_SetMainThread()202 void NS_SetMainThread() {
203   if (!sTLSIsMainThread.init()) {
204     MOZ_CRASH();
205   }
206   sTLSIsMainThread.set(true);
207   MOZ_ASSERT(NS_IsMainThread());
208 }
209 
210 #ifdef DEBUG
211 
212 namespace mozilla {
213 
AssertIsOnMainThread()214 void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); }
215 
216 }  // namespace mozilla
217 
218 #endif
219 
220 typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray;
221 
222 static bool sShutdownComplete;
223 
224 //-----------------------------------------------------------------------------
225 
226 /* static */
ReleaseThread(void * aData)227 void nsThreadManager::ReleaseThread(void* aData) {
228   if (sShutdownComplete) {
229     // We've already completed shutdown and released the references to all or
230     // our TLS wrappers. Don't try to release them again.
231     return;
232   }
233 
234   auto* thread = static_cast<nsThread*>(aData);
235 
236   if (thread->mHasTLSEntry) {
237     thread->mHasTLSEntry = false;
238     thread->Release();
239   }
240 }
241 
242 // statically allocated instance
NS_IMETHODIMP_(MozExternalRefCountType)243 NS_IMETHODIMP_(MozExternalRefCountType)
244 nsThreadManager::AddRef() { return 2; }
NS_IMETHODIMP_(MozExternalRefCountType)245 NS_IMETHODIMP_(MozExternalRefCountType)
246 nsThreadManager::Release() { return 1; }
247 NS_IMPL_CLASSINFO(nsThreadManager, nullptr,
248                   nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
249                   NS_THREADMANAGER_CID)
250 NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager)
251 NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager)
252 
253 namespace {
254 
255 // Simple observer to monitor the beginning of the shutdown.
256 class ShutdownObserveHelper final : public nsIObserver,
257                                     public nsSupportsWeakReference {
258  public:
259   NS_DECL_ISUPPORTS
260 
Create(ShutdownObserveHelper ** aObserver)261   static nsresult Create(ShutdownObserveHelper** aObserver) {
262     MOZ_ASSERT(aObserver);
263 
264     RefPtr<ShutdownObserveHelper> observer = new ShutdownObserveHelper();
265 
266     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
267     if (NS_WARN_IF(!obs)) {
268       return NS_ERROR_FAILURE;
269     }
270 
271     nsresult rv =
272         obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
273     if (NS_WARN_IF(NS_FAILED(rv))) {
274       return rv;
275     }
276 
277     rv = obs->AddObserver(observer, "content-child-will-shutdown", true);
278     if (NS_WARN_IF(NS_FAILED(rv))) {
279       return rv;
280     }
281 
282     observer.forget(aObserver);
283     return NS_OK;
284   }
285 
286   NS_IMETHOD
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)287   Observe(nsISupports* aSubject, const char* aTopic,
288           const char16_t* aData) override {
289     if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
290         !strcmp(aTopic, "content-child-will-shutdown")) {
291       mShuttingDown = true;
292       return NS_OK;
293     }
294 
295     return NS_OK;
296   }
297 
ShuttingDown() const298   bool ShuttingDown() const { return mShuttingDown; }
299 
300  private:
ShutdownObserveHelper()301   explicit ShutdownObserveHelper() : mShuttingDown(false) {}
302 
303   ~ShutdownObserveHelper() = default;
304 
305   bool mShuttingDown;
306 };
307 
308 NS_INTERFACE_MAP_BEGIN(ShutdownObserveHelper)
309   NS_INTERFACE_MAP_ENTRY(nsIObserver)
310   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
311   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
312 NS_INTERFACE_MAP_END
313 
314 NS_IMPL_ADDREF(ShutdownObserveHelper)
315 NS_IMPL_RELEASE(ShutdownObserveHelper)
316 
317 StaticRefPtr<ShutdownObserveHelper> gShutdownObserveHelper;
318 
319 }  // namespace
320 
321 //-----------------------------------------------------------------------------
322 
get()323 /*static*/ nsThreadManager& nsThreadManager::get() {
324   static nsThreadManager sInstance;
325   return sInstance;
326 }
327 
328 /* static */
InitializeShutdownObserver()329 void nsThreadManager::InitializeShutdownObserver() {
330   MOZ_ASSERT(!gShutdownObserveHelper);
331 
332   RefPtr<ShutdownObserveHelper> observer;
333   nsresult rv = ShutdownObserveHelper::Create(getter_AddRefs(observer));
334   if (NS_WARN_IF(NS_FAILED(rv))) {
335     return;
336   }
337 
338   gShutdownObserveHelper = observer;
339   ClearOnShutdown(&gShutdownObserveHelper);
340 }
341 
nsThreadManager()342 nsThreadManager::nsThreadManager()
343     : mCurThreadIndex(0), mMainPRThread(nullptr), mInitialized(false) {}
344 
345 nsThreadManager::~nsThreadManager() = default;
346 
Init()347 nsresult nsThreadManager::Init() {
348   // Child processes need to initialize the thread manager before they
349   // initialize XPCOM in order to set up the crash reporter. This leads to
350   // situations where we get initialized twice.
351   if (mInitialized) {
352     return NS_OK;
353   }
354 
355   if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseThread) == PR_FAILURE) {
356     return NS_ERROR_FAILURE;
357   }
358 
359 #ifdef MOZ_CANARY
360   const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
361   const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
362   char* env_var_flag = getenv("MOZ_KILL_CANARIES");
363   sCanaryOutputFD =
364       env_var_flag
365           ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO)
366           : 0;
367 #endif
368 
369   nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
370 
371   mMainThread =
372       CreateMainThread<ThreadEventQueue<PrioritizedEventQueue>>(idlePeriod);
373 
374   nsresult rv = mMainThread->InitCurrentThread();
375   if (NS_FAILED(rv)) {
376     mMainThread = nullptr;
377     return rv;
378   }
379 
380   // We need to keep a pointer to the current thread, so we can satisfy
381   // GetIsMainThread calls that occur post-Shutdown.
382   mMainThread->GetPRThread(&mMainPRThread);
383 
384   // Init AbstractThread.
385   AbstractThread::InitTLS();
386   AbstractThread::InitMainThread();
387 
388   // Initialize the background event target.
389   RefPtr<BackgroundEventTarget> target(new BackgroundEventTarget());
390 
391   rv = target->Init();
392   NS_ENSURE_SUCCESS(rv, rv);
393 
394   mBackgroundEventTarget = std::move(target);
395 
396   mInitialized = true;
397 
398   return NS_OK;
399 }
400 
Shutdown()401 void nsThreadManager::Shutdown() {
402   MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread");
403 
404   // Prevent further access to the thread manager (no more new threads!)
405   //
406   // What happens if shutdown happens before NewThread completes?
407   // We Shutdown() the new thread, and return error if we've started Shutdown
408   // between when NewThread started, and when the thread finished initializing
409   // and registering with ThreadManager.
410   //
411   mInitialized = false;
412 
413   // Empty the main thread event queue before we begin shutting down threads.
414   NS_ProcessPendingEvents(mMainThread);
415 
416   typedef typename ShutdownPromise::AllPromiseType AllPromise;
417   typename AllPromise::ResolveOrRejectValue val;
418   using ResolveValueT = typename AllPromise::ResolveValueType;
419   using RejectValueT = typename AllPromise::RejectValueType;
420 
421   nsTArray<RefPtr<ShutdownPromise>> promises;
422   mBackgroundEventTarget->BeginShutdown(promises);
423 
424   RefPtr<AllPromise> complete = ShutdownPromise::All(mMainThread, promises);
425 
426   bool taskQueuesShutdown = false;
427 
428   complete->Then(
429       mMainThread, __func__,
430       [&](const ResolveValueT& aResolveValue) {
431         mBackgroundEventTarget->FinishShutdown();
432         taskQueuesShutdown = true;
433       },
434       [&](RejectValueT aRejectValue) {
435         mBackgroundEventTarget->FinishShutdown();
436         taskQueuesShutdown = true;
437       });
438 
439   // Wait for task queues to shutdown, so we don't shut down the underlying
440   // threads of the background event target in the block below, thereby
441   // preventing the task queues from emptying, preventing the shutdown promises
442   // from resolving, and prevent anything checking `taskQueuesShutdown` from
443   // working.
444   ::SpinEventLoopUntil([&]() { return taskQueuesShutdown; }, mMainThread);
445 
446   {
447     // We gather the threads from the hashtable into a list, so that we avoid
448     // holding the enumerator lock while calling nsIThread::Shutdown.
449     nsTArray<RefPtr<nsThread>> threadsToShutdown;
450     for (auto* thread : nsThread::Enumerate()) {
451       if (thread->ShutdownRequired()) {
452         threadsToShutdown.AppendElement(thread);
453       }
454     }
455 
456     // It's tempting to walk the list of threads here and tell them each to stop
457     // accepting new events, but that could lead to badness if one of those
458     // threads is stuck waiting for a response from another thread.  To do it
459     // right, we'd need some way to interrupt the threads.
460     //
461     // Instead, we process events on the current thread while waiting for
462     // threads to shutdown.  This means that we have to preserve a mostly
463     // functioning world until such time as the threads exit.
464 
465     // Shutdown all threads that require it (join with threads that we created).
466     for (auto& thread : threadsToShutdown) {
467       thread->Shutdown();
468     }
469   }
470 
471   // NB: It's possible that there are events in the queue that want to *start*
472   // an asynchronous shutdown. But we have already shutdown the threads above,
473   // so there's no need to worry about them. We only have to wait for all
474   // in-flight asynchronous thread shutdowns to complete.
475   mMainThread->WaitForAllAsynchronousShutdowns();
476 
477   // In case there are any more events somehow...
478   NS_ProcessPendingEvents(mMainThread);
479 
480   // There are no more background threads at this point.
481 
482   // Normally thread shutdown clears the observer for the thread, but since the
483   // main thread is special we do it manually here after we're sure all events
484   // have been processed.
485   mMainThread->SetObserver(nullptr);
486 
487   mBackgroundEventTarget = nullptr;
488 
489   // Release main thread object.
490   mMainThread = nullptr;
491 
492   // Remove the TLS entry for the main thread.
493   PR_SetThreadPrivate(mCurThreadIndex, nullptr);
494 
495   {
496     // Cleanup the last references to any threads which haven't shut down yet.
497     nsTArray<RefPtr<nsThread>> threads;
498     for (auto* thread : nsThread::Enumerate()) {
499       if (thread->mHasTLSEntry) {
500         threads.AppendElement(dont_AddRef(thread));
501         thread->mHasTLSEntry = false;
502       }
503     }
504   }
505 
506   // xpcshell tests sometimes leak the main thread. They don't enable leak
507   // checking, so that doesn't cause the test to fail, but leaving the entry in
508   // the thread list triggers an assertion, which does.
509   nsThread::ClearThreadList();
510 
511   sShutdownComplete = true;
512 }
513 
RegisterCurrentThread(nsThread & aThread)514 void nsThreadManager::RegisterCurrentThread(nsThread& aThread) {
515   MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
516 
517   aThread.AddRef();  // for TLS entry
518   aThread.mHasTLSEntry = true;
519   PR_SetThreadPrivate(mCurThreadIndex, &aThread);
520 }
521 
UnregisterCurrentThread(nsThread & aThread)522 void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) {
523   MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
524 
525   PR_SetThreadPrivate(mCurThreadIndex, nullptr);
526   // Ref-count balanced via ReleaseThread
527 }
528 
CreateCurrentThread(SynchronizedEventQueue * aQueue,nsThread::MainThreadFlag aMainThread)529 nsThread* nsThreadManager::CreateCurrentThread(
530     SynchronizedEventQueue* aQueue, nsThread::MainThreadFlag aMainThread) {
531   // Make sure we don't have an nsThread yet.
532   MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex));
533 
534   if (!mInitialized) {
535     return nullptr;
536   }
537 
538   RefPtr<nsThread> thread = new nsThread(WrapNotNull(aQueue), aMainThread, 0);
539   if (!thread || NS_FAILED(thread->InitCurrentThread())) {
540     return nullptr;
541   }
542 
543   return thread.get();  // reference held in TLS
544 }
545 
DispatchToBackgroundThread(nsIRunnable * aEvent,uint32_t aDispatchFlags)546 nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent,
547                                                      uint32_t aDispatchFlags) {
548   if (!mInitialized) {
549     return NS_ERROR_FAILURE;
550   }
551 
552   nsCOMPtr<nsIEventTarget> backgroundTarget(mBackgroundEventTarget);
553   return backgroundTarget->Dispatch(aEvent, aDispatchFlags);
554 }
555 
556 already_AddRefed<nsISerialEventTarget>
CreateBackgroundTaskQueue(const char * aName)557 nsThreadManager::CreateBackgroundTaskQueue(const char* aName) {
558   if (!mInitialized) {
559     return nullptr;
560   }
561 
562   return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName);
563 }
564 
GetCurrentThread()565 nsThread* nsThreadManager::GetCurrentThread() {
566   // read thread local storage
567   void* data = PR_GetThreadPrivate(mCurThreadIndex);
568   if (data) {
569     return static_cast<nsThread*>(data);
570   }
571 
572   if (!mInitialized) {
573     return nullptr;
574   }
575 
576   // OK, that's fine.  We'll dynamically create one :-)
577   //
578   // We assume that if we're implicitly creating a thread here that it doesn't
579   // want an event queue. Any thread which wants an event queue should
580   // explicitly create its nsThread wrapper.
581   RefPtr<nsThread> thread = new nsThread();
582   if (!thread || NS_FAILED(thread->InitCurrentThread())) {
583     return nullptr;
584   }
585 
586   return thread.get();  // reference held in TLS
587 }
588 
IsNSThread() const589 bool nsThreadManager::IsNSThread() const {
590   if (!mInitialized) {
591     return false;
592   }
593   if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) {
594     return thread->EventQueue();
595   }
596   return false;
597 }
598 
599 NS_IMETHODIMP
NewThread(uint32_t aCreationFlags,uint32_t aStackSize,nsIThread ** aResult)600 nsThreadManager::NewThread(uint32_t aCreationFlags, uint32_t aStackSize,
601                            nsIThread** aResult) {
602   return NewNamedThread(NS_LITERAL_CSTRING(""), aStackSize, aResult);
603 }
604 
605 NS_IMETHODIMP
NewNamedThread(const nsACString & aName,uint32_t aStackSize,nsIThread ** aResult)606 nsThreadManager::NewNamedThread(const nsACString& aName, uint32_t aStackSize,
607                                 nsIThread** aResult) {
608   // Note: can be called from arbitrary threads
609 
610   // No new threads during Shutdown
611   if (NS_WARN_IF(!mInitialized)) {
612     return NS_ERROR_NOT_INITIALIZED;
613   }
614 
615   RefPtr<ThreadEventQueue<EventQueue>> queue =
616       new ThreadEventQueue<EventQueue>(MakeUnique<EventQueue>());
617   RefPtr<nsThread> thr =
618       new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aStackSize);
619   nsresult rv =
620       thr->Init(aName);  // Note: blocks until the new thread has been set up
621   if (NS_FAILED(rv)) {
622     return rv;
623   }
624 
625   // At this point, we expect that the thread has been registered in
626   // mThreadByPRThread; however, it is possible that it could have also been
627   // replaced by now, so we cannot really assert that it was added.  Instead,
628   // kill it if we entered Shutdown() during/before Init()
629 
630   if (NS_WARN_IF(!mInitialized)) {
631     if (thr->ShutdownRequired()) {
632       thr->Shutdown();  // ok if it happens multiple times
633     }
634     return NS_ERROR_NOT_INITIALIZED;
635   }
636 
637   thr.forget(aResult);
638   return NS_OK;
639 }
640 
641 NS_IMETHODIMP
GetMainThread(nsIThread ** aResult)642 nsThreadManager::GetMainThread(nsIThread** aResult) {
643   // Keep this functioning during Shutdown
644   if (!mMainThread) {
645     if (!NS_IsMainThread()) {
646       NS_WARNING(
647           "Called GetMainThread but there isn't a main thread and "
648           "we're not the main thread.");
649     }
650     return NS_ERROR_NOT_INITIALIZED;
651   }
652   NS_ADDREF(*aResult = mMainThread);
653   return NS_OK;
654 }
655 
656 NS_IMETHODIMP
GetCurrentThread(nsIThread ** aResult)657 nsThreadManager::GetCurrentThread(nsIThread** aResult) {
658   // Keep this functioning during Shutdown
659   if (!mMainThread) {
660     return NS_ERROR_NOT_INITIALIZED;
661   }
662   *aResult = GetCurrentThread();
663   if (!*aResult) {
664     return NS_ERROR_OUT_OF_MEMORY;
665   }
666   NS_ADDREF(*aResult);
667   return NS_OK;
668 }
669 
670 NS_IMETHODIMP
SpinEventLoopUntil(nsINestedEventLoopCondition * aCondition)671 nsThreadManager::SpinEventLoopUntil(nsINestedEventLoopCondition* aCondition) {
672   return SpinEventLoopUntilInternal(aCondition, false);
673 }
674 
675 NS_IMETHODIMP
SpinEventLoopUntilOrShutdown(nsINestedEventLoopCondition * aCondition)676 nsThreadManager::SpinEventLoopUntilOrShutdown(
677     nsINestedEventLoopCondition* aCondition) {
678   return SpinEventLoopUntilInternal(aCondition, true);
679 }
680 
SpinEventLoopUntilInternal(nsINestedEventLoopCondition * aCondition,bool aCheckingShutdown)681 nsresult nsThreadManager::SpinEventLoopUntilInternal(
682     nsINestedEventLoopCondition* aCondition, bool aCheckingShutdown) {
683   nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition);
684   nsresult rv = NS_OK;
685 
686   // Nothing to do if already shutting down. Note that gShutdownObserveHelper is
687   // nullified on shutdown.
688   if (aCheckingShutdown &&
689       (!gShutdownObserveHelper || gShutdownObserveHelper->ShuttingDown())) {
690     return NS_OK;
691   }
692 
693   if (!mozilla::SpinEventLoopUntil([&]() -> bool {
694         // Shutting down is started.
695         if (aCheckingShutdown && (!gShutdownObserveHelper ||
696                                   gShutdownObserveHelper->ShuttingDown())) {
697           return true;
698         }
699 
700         bool isDone = false;
701         rv = condition->IsDone(&isDone);
702         // JS failure should be unusual, but we need to stop and propagate
703         // the error back to the caller.
704         if (NS_FAILED(rv)) {
705           return true;
706         }
707 
708         return isDone;
709       })) {
710     // We stopped early for some reason, which is unexpected.
711     return NS_ERROR_UNEXPECTED;
712   }
713 
714   // If we exited when the condition told us to, we need to return whether
715   // the condition encountered failure when executing.
716   return rv;
717 }
718 
719 NS_IMETHODIMP
SpinEventLoopUntilEmpty()720 nsThreadManager::SpinEventLoopUntilEmpty() {
721   nsIThread* thread = NS_GetCurrentThread();
722 
723   while (NS_HasPendingEvents(thread)) {
724     (void)NS_ProcessNextEvent(thread, false);
725   }
726 
727   return NS_OK;
728 }
729 
730 NS_IMETHODIMP
GetMainThreadEventTarget(nsIEventTarget ** aTarget)731 nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) {
732   nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget();
733   target.forget(aTarget);
734   return NS_OK;
735 }
736 
GetHighestNumberOfThreads()737 uint32_t nsThreadManager::GetHighestNumberOfThreads() {
738   return nsThread::MaxActiveThreads();
739 }
740 
741 NS_IMETHODIMP
DispatchToMainThread(nsIRunnable * aEvent,uint32_t aPriority,uint8_t aArgc)742 nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority,
743                                       uint8_t aArgc) {
744   // Note: C++ callers should instead use NS_DispatchToMainThread.
745   MOZ_ASSERT(NS_IsMainThread());
746 
747   // Keep this functioning during Shutdown
748   if (NS_WARN_IF(!mMainThread)) {
749     return NS_ERROR_NOT_INITIALIZED;
750   }
751   // If aPriority wasn't explicitly passed, that means it should be treated as
752   // PRIORITY_NORMAL.
753   if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) {
754     nsCOMPtr<nsIRunnable> event(aEvent);
755     return mMainThread->DispatchFromScript(
756         new PrioritizableRunnable(event.forget(), aPriority), 0);
757   }
758   return mMainThread->DispatchFromScript(aEvent, 0);
759 }
760 
EnableMainThreadEventPrioritization()761 void nsThreadManager::EnableMainThreadEventPrioritization() {
762   MOZ_ASSERT(NS_IsMainThread());
763   InputEventStatistics::Get().SetEnable(true);
764   mMainThread->EnableInputEventPrioritization();
765 }
766 
FlushInputEventPrioritization()767 void nsThreadManager::FlushInputEventPrioritization() {
768   MOZ_ASSERT(NS_IsMainThread());
769   mMainThread->FlushInputEventPrioritization();
770 }
771 
SuspendInputEventPrioritization()772 void nsThreadManager::SuspendInputEventPrioritization() {
773   MOZ_ASSERT(NS_IsMainThread());
774   mMainThread->SuspendInputEventPrioritization();
775 }
776 
ResumeInputEventPrioritization()777 void nsThreadManager::ResumeInputEventPrioritization() {
778   MOZ_ASSERT(NS_IsMainThread());
779   mMainThread->ResumeInputEventPrioritization();
780 }
781 
782 // static
MainThreadHasPendingHighPriorityEvents()783 bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() {
784   MOZ_ASSERT(NS_IsMainThread());
785   bool retVal = false;
786   if (get().mMainThread) {
787     get().mMainThread->HasPendingHighPriorityEvents(&retVal);
788   }
789   return retVal;
790 }
791 
792 NS_IMETHODIMP
IdleDispatchToMainThread(nsIRunnable * aEvent,uint32_t aTimeout)793 nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent,
794                                           uint32_t aTimeout) {
795   // Note: C++ callers should instead use NS_DispatchToThreadQueue or
796   // NS_DispatchToCurrentThreadQueue.
797   MOZ_ASSERT(NS_IsMainThread());
798 
799   nsCOMPtr<nsIRunnable> event(aEvent);
800   if (aTimeout) {
801     return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread,
802                                     EventQueuePriority::Idle);
803   }
804 
805   return NS_DispatchToThreadQueue(event.forget(), mMainThread,
806                                   EventQueuePriority::Idle);
807 }
808