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