Bug 1925561 - Use a quicker radius calculation for ArcParams. r=aosmond
[gecko.git] / xpcom / threads / nsThread.cpp
blob1bfae5d55fecae6a6610c46978720358a326847b
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 "nsThread.h"
9 #include "base/message_loop.h"
10 #include "base/platform_thread.h"
12 // Chromium's logging can sometimes leak through...
13 #ifdef LOG
14 # undef LOG
15 #endif
17 #include "mozilla/ReentrantMonitor.h"
18 #include "nsMemoryPressure.h"
19 #include "nsThreadManager.h"
20 #include "nsIClassInfoImpl.h"
21 #include "nsCOMPtr.h"
22 #include "nsQueryObject.h"
23 #include "pratom.h"
24 #include "mozilla/BackgroundHangMonitor.h"
25 #include "mozilla/CycleCollectedJSContext.h"
26 #include "mozilla/DebugOnly.h"
27 #include "mozilla/Logging.h"
28 #include "nsIObserverService.h"
29 #include "mozilla/IOInterposer.h"
30 #include "mozilla/ipc/MessageChannel.h"
31 #include "mozilla/ipc/BackgroundChild.h"
32 #include "mozilla/Preferences.h"
33 #include "mozilla/ProfilerRunnable.h"
34 #include "mozilla/SchedulerGroup.h"
35 #include "mozilla/Services.h"
36 #include "mozilla/SpinEventLoopUntil.h"
37 #include "mozilla/StaticLocalPtr.h"
38 #include "mozilla/StaticPrefs_threads.h"
39 #include "mozilla/TaskController.h"
40 #include "nsXPCOMPrivate.h"
41 #include "mozilla/ChaosMode.h"
42 #include "mozilla/Telemetry.h"
43 #include "mozilla/TimeStamp.h"
44 #include "mozilla/Unused.h"
45 #include "mozilla/dom/DocGroup.h"
46 #include "mozilla/dom/ScriptSettings.h"
47 #include "nsThreadSyncDispatch.h"
48 #include "nsServiceManagerUtils.h"
49 #include "GeckoProfiler.h"
50 #include "ThreadEventQueue.h"
51 #include "ThreadEventTarget.h"
52 #include "ThreadDelay.h"
54 #include <limits>
56 #ifdef XP_LINUX
57 # ifdef __GLIBC__
58 # include <gnu/libc-version.h>
59 # endif
60 # include <sys/mman.h>
61 # include <sys/time.h>
62 # include <sys/resource.h>
63 # include <sched.h>
64 # include <stdio.h>
65 #endif
67 #ifdef XP_WIN
68 # include "mozilla/DynamicallyLinkedFunctionPtr.h"
70 # include <winbase.h>
72 using GetCurrentThreadStackLimitsFn = void(WINAPI*)(PULONG_PTR LowLimit,
73 PULONG_PTR HighLimit);
74 #endif
76 #define HAVE_UALARM \
77 _BSD_SOURCE || \
78 (_XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
79 !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
81 #if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE)
82 # define HAVE_SCHED_SETAFFINITY
83 #endif
85 #ifdef XP_MACOSX
86 # include <mach/mach.h>
87 # include <mach/thread_policy.h>
88 # include <sys/qos.h>
89 #endif
91 #ifdef MOZ_CANARY
92 # include <unistd.h>
93 # include <execinfo.h>
94 # include <signal.h>
95 # include <fcntl.h>
96 # include "nsXULAppAPI.h"
97 #endif
99 using namespace mozilla;
101 extern void InitThreadLocalVariables();
103 static LazyLogModule sThreadLog("nsThread");
104 #ifdef LOG
105 # undef LOG
106 #endif
107 #define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args)
109 NS_DECL_CI_INTERFACE_GETTER(nsThread)
111 Array<char, nsThread::kRunnableNameBufSize> nsThread::sMainThreadRunnableName;
113 //-----------------------------------------------------------------------------
114 // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
115 // somewhat manually.
117 class nsThreadClassInfo : public nsIClassInfo {
118 public:
119 NS_DECL_ISUPPORTS_INHERITED // no mRefCnt
120 NS_DECL_NSICLASSINFO
122 nsThreadClassInfo() = default;
125 NS_IMETHODIMP_(MozExternalRefCountType)
126 nsThreadClassInfo::AddRef() { return 2; }
127 NS_IMETHODIMP_(MozExternalRefCountType)
128 nsThreadClassInfo::Release() { return 1; }
129 NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo)
131 NS_IMETHODIMP
132 nsThreadClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) {
133 return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aArray);
136 NS_IMETHODIMP
137 nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) {
138 *aResult = nullptr;
139 return NS_OK;
142 NS_IMETHODIMP
143 nsThreadClassInfo::GetContractID(nsACString& aResult) {
144 aResult.SetIsVoid(true);
145 return NS_OK;
148 NS_IMETHODIMP
149 nsThreadClassInfo::GetClassDescription(nsACString& aResult) {
150 aResult.SetIsVoid(true);
151 return NS_OK;
154 NS_IMETHODIMP
155 nsThreadClassInfo::GetClassID(nsCID** aResult) {
156 *aResult = nullptr;
157 return NS_OK;
160 NS_IMETHODIMP
161 nsThreadClassInfo::GetFlags(uint32_t* aResult) {
162 *aResult = THREADSAFE;
163 return NS_OK;
166 NS_IMETHODIMP
167 nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) {
168 return NS_ERROR_NOT_AVAILABLE;
171 //-----------------------------------------------------------------------------
173 NS_IMPL_ADDREF(nsThread)
174 NS_IMPL_RELEASE(nsThread)
175 NS_INTERFACE_MAP_BEGIN(nsThread)
176 NS_INTERFACE_MAP_ENTRY(nsIThread)
177 NS_INTERFACE_MAP_ENTRY(nsIThreadInternal)
178 NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
179 NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
180 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
181 NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher)
182 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
183 if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
184 static nsThreadClassInfo sThreadClassInfo;
185 foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo);
186 } else
187 NS_INTERFACE_MAP_END
188 NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal,
189 nsIEventTarget, nsISerialEventTarget,
190 nsISupportsPriority)
192 //-----------------------------------------------------------------------------
194 // This event is responsible for notifying nsThread::Shutdown that it is time
195 // to call PR_JoinThread. It implements nsICancelableRunnable so that it can
196 // run on a DOM Worker thread (where all events must implement
197 // nsICancelableRunnable.)
198 class nsThreadShutdownAckEvent : public CancelableRunnable {
199 public:
200 explicit nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext*> aCtx)
201 : CancelableRunnable("nsThreadShutdownAckEvent"),
202 mShutdownContext(aCtx) {}
203 NS_IMETHOD Run() override {
204 mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext);
205 return NS_OK;
207 nsresult Cancel() override { return Run(); }
209 private:
210 virtual ~nsThreadShutdownAckEvent() = default;
212 NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext;
215 // This event is responsible for setting mShutdownContext
216 class nsThreadShutdownEvent : public Runnable {
217 public:
218 nsThreadShutdownEvent(NotNull<nsThread*> aThr,
219 NotNull<nsThreadShutdownContext*> aCtx)
220 : Runnable("nsThreadShutdownEvent"),
221 mThread(aThr),
222 mShutdownContext(aCtx) {}
223 NS_IMETHOD Run() override {
224 // Creates a cycle between `mThread` and the shutdown context which will be
225 // broken when the thread exits.
226 mThread->mShutdownContext = mShutdownContext;
227 MessageLoop::current()->Quit();
228 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
229 // Let's leave a trace that we passed here in the thread's name.
230 nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread()));
231 threadName.Append(",SHDRCV"_ns);
232 NS_SetCurrentThreadName(threadName.get());
233 #endif
234 return NS_OK;
237 private:
238 NotNull<RefPtr<nsThread>> mThread;
239 NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext;
242 //-----------------------------------------------------------------------------
244 static void SetThreadAffinity(unsigned int cpu) {
245 #ifdef HAVE_SCHED_SETAFFINITY
246 cpu_set_t cpus;
247 CPU_ZERO(&cpus);
248 CPU_SET(cpu, &cpus);
249 sched_setaffinity(0, sizeof(cpus), &cpus);
250 // Don't assert sched_setaffinity's return value because it intermittently (?)
251 // fails with EINVAL on Linux x64 try runs.
252 #elif defined(XP_MACOSX)
253 // OS X does not provide APIs to pin threads to specific processors, but you
254 // can tag threads as belonging to the same "affinity set" and the OS will try
255 // to run them on the same processor. To run threads on different processors,
256 // tag them as belonging to different affinity sets. Tag 0, the default, means
257 // "no affinity" so let's pretend each CPU has its own tag `cpu+1`.
258 thread_affinity_policy_data_t policy;
259 policy.affinity_tag = cpu + 1;
260 kern_return_t kr = thread_policy_set(
261 mach_thread_self(), THREAD_AFFINITY_POLICY, &policy.affinity_tag, 1);
262 // Setting the thread affinity is not supported on ARM.
263 MOZ_ALWAYS_TRUE(kr == KERN_SUCCESS || kr == KERN_NOT_SUPPORTED);
264 #elif defined(XP_WIN)
265 MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) !=
266 (DWORD)-1);
267 #endif
270 static void SetupCurrentThreadForChaosMode() {
271 if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
272 return;
275 #ifdef XP_LINUX
276 // PR_SetThreadPriority doesn't really work since priorities >
277 // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use
278 // setpriority(2) to set random 'nice values'. In regular Linux this is only
279 // a dynamic adjustment so it still doesn't really do what we want, but tools
280 // like 'rr' can be more aggressive about honoring these values.
281 // Some of these calls may fail due to trying to lower the priority
282 // (e.g. something may have already called setpriority() for this thread).
283 // This makes it hard to have non-main threads with higher priority than the
284 // main thread, but that's hard to fix. Tools like rr can choose to honor the
285 // requested values anyway.
286 // Use just 4 priorities so there's a reasonable chance of any two threads
287 // having equal priority.
288 setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4));
289 #else
290 // We should set the affinity here but NSPR doesn't provide a way to expose
291 // it.
292 uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1);
293 PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority));
294 #endif
296 // Force half the threads to CPU 0 so they compete for CPU
297 if (ChaosMode::randomUint32LessThan(2)) {
298 SetThreadAffinity(0);
302 namespace {
304 struct ThreadInitData {
305 RefPtr<nsThread> thread;
306 nsCString name;
309 } // namespace
311 void nsThread::MaybeRemoveFromThreadList() {
312 nsThreadManager& tm = nsThreadManager::get();
313 OffTheBooksMutexAutoLock mal(tm.ThreadListMutex());
314 if (isInList()) {
315 removeFrom(tm.ThreadList());
319 /*static*/
320 void nsThread::ThreadFunc(void* aArg) {
321 using mozilla::ipc::BackgroundChild;
323 UniquePtr<ThreadInitData> initData(static_cast<ThreadInitData*>(aArg));
324 RefPtr<nsThread>& self = initData->thread;
326 MOZ_ASSERT(self->mEventTarget);
327 MOZ_ASSERT(self->mEvents);
329 // Note: see the comment in nsThread::Init, where we set these same values.
330 DebugOnly<PRThread*> prev = self->mThread.exchange(PR_GetCurrentThread());
331 MOZ_ASSERT(!prev || prev == PR_GetCurrentThread());
332 self->mEventTarget->SetCurrentThread(self->mThread);
333 SetupCurrentThreadForChaosMode();
335 if (!initData->name.IsEmpty()) {
336 NS_SetCurrentThreadName(initData->name.BeginReading());
339 self->InitCommon();
341 // Inform the ThreadManager
342 nsThreadManager::get().RegisterCurrentThread(*self);
344 mozilla::IOInterposer::RegisterCurrentThread();
346 // This must come after the call to nsThreadManager::RegisterCurrentThread(),
347 // because that call is needed to properly set up this thread as an nsThread,
348 // which profiler_register_thread() requires. See bug 1347007.
349 const bool registerWithProfiler = !initData->name.IsEmpty();
350 if (registerWithProfiler) {
351 PROFILER_REGISTER_THREAD(initData->name.BeginReading());
355 // Scope for MessageLoop.
356 MessageLoop loop(
357 #if defined(XP_WIN) || defined(XP_MACOSX)
358 self->mIsUiThread ? MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD
359 : MessageLoop::TYPE_MOZILLA_NONMAINTHREAD,
360 #else
361 MessageLoop::TYPE_MOZILLA_NONMAINTHREAD,
362 #endif
363 self);
365 // Now, process incoming events...
366 loop.Run();
368 self->mEvents->RunShutdownTasks();
370 BackgroundChild::CloseForCurrentThread();
372 // NB: The main thread does not shut down here! It shuts down via
373 // nsThreadManager::Shutdown.
375 // Do NS_ProcessPendingEvents but with special handling to set
376 // mEventsAreDoomed atomically with the removal of the last event. The key
377 // invariant here is that we will never permit PutEvent to succeed if the
378 // event would be left in the queue after our final call to
379 // NS_ProcessPendingEvents. We also have to keep processing events as long
380 // as we have outstanding mRequestedShutdownContexts.
381 while (true) {
382 // Check and see if we're waiting on any threads.
383 self->WaitForAllAsynchronousShutdowns();
385 if (self->mEvents->ShutdownIfNoPendingEvents()) {
386 break;
388 NS_ProcessPendingEvents(self);
392 mozilla::IOInterposer::UnregisterCurrentThread();
394 // Inform the threadmanager that this thread is going away
395 nsThreadManager::get().UnregisterCurrentThread(*self);
397 // The thread should only unregister itself if it was registered above.
398 if (registerWithProfiler) {
399 PROFILER_UNREGISTER_THREAD();
402 NotNull<RefPtr<nsThreadShutdownContext>> context =
403 WrapNotNull(self->mShutdownContext);
404 self->mShutdownContext = nullptr;
405 MOZ_ASSERT(context->mTerminatingThread == self);
407 // Take the joining thread from our shutdown context. This may have been
408 // cleared by the joining thread if it decided to cancel waiting on us, in
409 // which case we won't notify our caller, and leak.
410 RefPtr<nsThread> joiningThread;
412 MutexAutoLock lock(context->mJoiningThreadMutex);
413 joiningThread = context->mJoiningThread.forget();
414 MOZ_RELEASE_ASSERT(joiningThread || context->mThreadLeaked);
416 if (joiningThread) {
417 // Dispatch shutdown ACK
418 nsCOMPtr<nsIRunnable> event = new nsThreadShutdownAckEvent(context);
419 nsresult dispatch_ack_rv =
420 joiningThread->Dispatch(event, NS_DISPATCH_NORMAL);
422 // We do not expect this to ever happen, but If we cannot dispatch
423 // the ack event, someone probably blocks waiting on us and will
424 // crash with a hang later anyways. The best we can do is to tell
425 // the world what happened right here.
426 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(dispatch_ack_rv));
428 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
429 // Let's leave a trace that we passed here in the thread's name.
430 nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread()));
431 threadName.Append(",SHDACK"_ns);
432 NS_SetCurrentThreadName(threadName.get());
433 #endif
434 } else {
435 NS_WARNING(
436 "nsThread exiting after StopWaitingAndLeakThread was called, thread "
437 "resources will be leaked!");
440 // Release any observer of the thread here.
441 self->SetObserver(nullptr);
443 // The PRThread will be deleted in PR_JoinThread(), so clear references.
444 self->mThread = nullptr;
445 self->mEventTarget->ClearCurrentThread();
448 void nsThread::InitCommon() {
449 mThreadId = uint32_t(PlatformThread::CurrentId());
452 #if defined(XP_LINUX)
453 pthread_attr_t attr;
454 int res = pthread_attr_init(&attr);
455 MOZ_RELEASE_ASSERT(!res);
456 res = pthread_getattr_np(pthread_self(), &attr);
457 MOZ_RELEASE_ASSERT(!res);
459 size_t stackSize;
460 res = pthread_attr_getstack(&attr, &mStackBase, &stackSize);
461 MOZ_RELEASE_ASSERT(!res);
463 // Glibc prior to 2.27 reports the stack size and base including the guard
464 // region, so we need to compensate for it to get accurate accounting.
465 // Also, this behavior difference isn't guarded by a versioned symbol, so we
466 // actually need to check the runtime glibc version, not the version we were
467 // compiled against.
468 static bool sAdjustForGuardSize = ({
469 # ifdef __GLIBC__
470 unsigned major, minor;
471 sscanf(gnu_get_libc_version(), "%u.%u", &major, &minor) < 2 ||
472 major < 2 || (major == 2 && minor < 27);
473 # else
474 false;
475 # endif
477 if (sAdjustForGuardSize) {
478 size_t guardSize;
479 res = pthread_attr_getguardsize(&attr, &guardSize);
480 MOZ_RELEASE_ASSERT(!res);
482 // Note: This assumes that the stack grows down, as is the case on all of
483 // our tier 1 platforms. On platforms where the stack grows up, the
484 // mStackBase adjustment is unnecessary, but doesn't cause any harm other
485 // than under-counting stack memory usage by one page.
486 mStackBase = reinterpret_cast<char*>(mStackBase) + guardSize;
487 stackSize -= guardSize;
490 mStackSize = stackSize;
492 // This is a bit of a hack.
494 // We really do want the NOHUGEPAGE flag on our thread stacks, since we
495 // don't expect any of them to need anywhere near 2MB of space. But setting
496 // it here is too late to have an effect, since the first stack page has
497 // already been faulted in existence, and NSPR doesn't give us a way to set
498 // it beforehand.
500 // What this does get us, however, is a different set of VM flags on our
501 // thread stacks compared to normal heap memory. Which makes the Linux
502 // kernel report them as separate regions, even when they are adjacent to
503 // heap memory. This allows us to accurately track the actual memory
504 // consumption of our allocated stacks.
505 madvise(mStackBase, stackSize, MADV_NOHUGEPAGE);
507 res = pthread_attr_destroy(&attr);
508 MOZ_RELEASE_ASSERT(!res);
509 #elif defined(XP_WIN)
510 static const StaticDynamicallyLinkedFunctionPtr<
511 GetCurrentThreadStackLimitsFn>
512 sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits");
514 if (sGetStackLimits) {
515 ULONG_PTR stackBottom, stackTop;
516 sGetStackLimits(&stackBottom, &stackTop);
517 mStackBase = reinterpret_cast<void*>(stackBottom);
518 mStackSize = stackTop - stackBottom;
520 #endif
523 InitThreadLocalVariables();
526 //-----------------------------------------------------------------------------
528 #ifdef MOZ_CANARY
529 int sCanaryOutputFD = -1;
530 #endif
532 nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue,
533 MainThreadFlag aMainThread,
534 nsIThreadManager::ThreadCreationOptions aOptions)
535 : mEvents(aQueue.get()),
536 mEventTarget(new ThreadEventTarget(
537 mEvents.get(), aMainThread == MAIN_THREAD, aOptions.blockDispatch)),
538 mOutstandingShutdownContexts(0),
539 mShutdownContext(nullptr),
540 mScriptObserver(nullptr),
541 mThreadName("<uninitialized>"),
542 mStackSize(aOptions.stackSize),
543 mNestedEventLoopDepth(0),
544 mShutdownRequired(false),
545 mPriority(PRIORITY_NORMAL),
546 mIsMainThread(aMainThread == MAIN_THREAD),
547 mUseHangMonitor(aMainThread == MAIN_THREAD),
548 mIsUiThread(aOptions.isUiThread),
549 mIsAPoolThreadFree(nullptr),
550 mCanInvokeJS(false),
551 mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread,
552 aOptions.longTaskLength) {
553 #if !(defined(XP_WIN) || defined(XP_MACOSX))
554 MOZ_ASSERT(!mIsUiThread,
555 "Non-main UI threads are only supported on Windows and macOS");
556 #endif
557 if (mIsMainThread) {
558 MOZ_ASSERT(!mIsUiThread,
559 "Setting isUIThread is not supported for main threads");
560 mozilla::TaskController::Get()->SetPerformanceCounterState(
561 &mPerformanceCounterState);
565 nsThread::nsThread()
566 : mEvents(nullptr),
567 mEventTarget(nullptr),
568 mOutstandingShutdownContexts(0),
569 mShutdownContext(nullptr),
570 mScriptObserver(nullptr),
571 mThreadName("<uninitialized>"),
572 mStackSize(0),
573 mNestedEventLoopDepth(0),
574 mShutdownRequired(false),
575 mPriority(PRIORITY_NORMAL),
576 mIsMainThread(false),
577 mUseHangMonitor(false),
578 mIsUiThread(false),
579 mCanInvokeJS(false),
580 mPerformanceCounterState(mNestedEventLoopDepth) {
581 MOZ_ASSERT(!NS_IsMainThread());
584 nsThread::~nsThread() {
585 NS_ASSERTION(mOutstandingShutdownContexts == 0,
586 "shouldn't be waiting on other threads to shutdown");
588 MaybeRemoveFromThreadList();
591 nsresult nsThread::Init(const nsACString& aName) {
592 MOZ_ASSERT(mEvents);
593 MOZ_ASSERT(mEventTarget);
594 MOZ_ASSERT(!mThread);
596 SetThreadNameInternal(aName);
598 PRThread* thread = nullptr;
600 nsThreadManager& tm = nsThreadManager::get();
602 OffTheBooksMutexAutoLock lock(tm.ThreadListMutex());
603 if (!tm.AllowNewXPCOMThreadsLocked()) {
604 return NS_ERROR_NOT_INITIALIZED;
607 // We need to fully start the thread while holding the thread list lock, as
608 // the next acquire of the lock could try to shut down this thread (e.g.
609 // during xpcom shutdown), which would hang if `PR_CreateThread` failed.
611 UniquePtr<ThreadInitData> initData(
612 new ThreadInitData{this, nsCString(aName)});
614 // ThreadFunc is responsible for setting mThread
615 if (!(thread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, initData.get(),
616 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
617 PR_JOINABLE_THREAD, mStackSize))) {
618 return NS_ERROR_OUT_OF_MEMORY;
621 // The created thread now owns initData, so release our ownership of it.
622 Unused << initData.release();
624 // The thread has successfully started, so we can mark it as requiring
625 // shutdown & add it to the thread list.
626 mShutdownRequired = true;
627 tm.ThreadList().insertBack(this);
630 // Note: we set these both here and inside ThreadFunc, to what should be
631 // the same value. This is because calls within ThreadFunc need these values
632 // to be set, and our callers need these values to be set.
633 DebugOnly<PRThread*> prev = mThread.exchange(thread);
634 MOZ_ASSERT(!prev || prev == thread);
636 mEventTarget->SetCurrentThread(thread);
637 return NS_OK;
640 nsresult nsThread::InitCurrentThread() {
641 mThread = PR_GetCurrentThread();
643 nsThreadManager& tm = nsThreadManager::get();
645 OffTheBooksMutexAutoLock lock(tm.ThreadListMutex());
646 // NOTE: We don't check AllowNewXPCOMThreads here, as threads initialized
647 // this way do not need shutdown, so are OK to create after nsThreadManager
648 // shutdown. In addition, the main thread is initialized this way, which
649 // happens before AllowNewXPCOMThreads begins to return true.
650 tm.ThreadList().insertBack(this);
653 SetupCurrentThreadForChaosMode();
654 InitCommon();
656 tm.RegisterCurrentThread(*this);
657 return NS_OK;
660 void nsThread::GetThreadName(nsACString& aNameBuffer) {
661 auto lock = mThreadName.Lock();
662 aNameBuffer = lock.ref();
665 void nsThread::SetThreadNameInternal(const nsACString& aName) {
666 auto lock = mThreadName.Lock();
667 lock->Assign(aName);
670 //-----------------------------------------------------------------------------
671 // nsIEventTarget
673 NS_IMETHODIMP
674 nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
675 MOZ_ASSERT(mEventTarget);
676 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
678 nsCOMPtr<nsIRunnable> event(aEvent);
679 return mEventTarget->Dispatch(event.forget(), aFlags);
682 NS_IMETHODIMP
683 nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
684 MOZ_ASSERT(mEventTarget);
685 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
687 LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */ nullptr, aFlags));
689 return mEventTarget->Dispatch(std::move(aEvent), aFlags);
692 NS_IMETHODIMP
693 nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
694 uint32_t aDelayMs) {
695 MOZ_ASSERT(mEventTarget);
696 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
698 return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs);
701 NS_IMETHODIMP
702 nsThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
703 MOZ_ASSERT(mEventTarget);
704 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
706 return mEventTarget->RegisterShutdownTask(aTask);
709 NS_IMETHODIMP
710 nsThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
711 MOZ_ASSERT(mEventTarget);
712 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
714 return mEventTarget->UnregisterShutdownTask(aTask);
717 NS_IMETHODIMP
718 nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
719 if (mIsAPoolThreadFree && *mIsAPoolThreadFree) {
720 // if there are unstarted threads in the pool, a new event to the
721 // pool would not be delayed at all (beyond thread start time)
722 *aDelay = TimeDuration();
723 *aStart = TimeStamp();
724 } else {
725 *aDelay = mLastEventDelay;
726 *aStart = mLastEventStart;
728 return NS_OK;
731 NS_IMETHODIMP
732 nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
733 mLastEventDelay = aDelay;
734 mLastEventStart = aStart;
735 return NS_OK;
738 NS_IMETHODIMP
739 nsThread::IsOnCurrentThread(bool* aResult) {
740 if (mEventTarget) {
741 return mEventTarget->IsOnCurrentThread(aResult);
743 *aResult = PR_GetCurrentThread() == mThread;
744 return NS_OK;
747 NS_IMETHODIMP_(bool)
748 nsThread::IsOnCurrentThreadInfallible() {
749 // This method is only going to be called if `mThread` is null, which
750 // only happens when the thread has exited the event loop. Therefore, when
751 // we are called, we can never be on this thread.
752 return false;
755 //-----------------------------------------------------------------------------
756 // nsIThread
758 NS_IMETHODIMP
759 nsThread::GetPRThread(PRThread** aResult) {
760 PRThread* thread = mThread; // atomic load
761 *aResult = thread;
762 return thread ? NS_OK : NS_ERROR_NOT_AVAILABLE;
765 NS_IMETHODIMP
766 nsThread::GetCanInvokeJS(bool* aResult) {
767 *aResult = mCanInvokeJS;
768 return NS_OK;
771 NS_IMETHODIMP
772 nsThread::SetCanInvokeJS(bool aCanInvokeJS) {
773 mCanInvokeJS = aCanInvokeJS;
774 return NS_OK;
777 NS_IMETHODIMP
778 nsThread::GetLastLongTaskEnd(TimeStamp* _retval) {
779 *_retval = mPerformanceCounterState.LastLongTaskEnd();
780 return NS_OK;
783 NS_IMETHODIMP
784 nsThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) {
785 *_retval = mPerformanceCounterState.LastLongNonIdleTaskEnd();
786 return NS_OK;
789 NS_IMETHODIMP
790 nsThread::AsyncShutdown() {
791 LOG(("THRD(%p) async shutdown\n", this));
793 nsCOMPtr<nsIThreadShutdown> shutdown;
794 BeginShutdown(getter_AddRefs(shutdown));
795 return NS_OK;
798 NS_IMETHODIMP
799 nsThread::BeginShutdown(nsIThreadShutdown** aShutdown) {
800 LOG(("THRD(%p) begin shutdown\n", this));
802 MOZ_ASSERT(mEvents);
803 MOZ_ASSERT(mEventTarget);
804 MOZ_ASSERT(mThread != PR_GetCurrentThread());
805 if (NS_WARN_IF(mThread == PR_GetCurrentThread())) {
806 return NS_ERROR_UNEXPECTED;
809 // Prevent multiple calls to this method.
810 if (!mShutdownRequired.compareExchange(true, false)) {
811 return NS_ERROR_UNEXPECTED;
813 MOZ_ASSERT(mThread);
815 RefPtr<nsThread> currentThread = nsThreadManager::get().GetCurrentThread();
817 MOZ_DIAGNOSTIC_ASSERT(currentThread->EventQueue(),
818 "Shutdown() may only be called from an XPCOM thread");
820 // Allocate a shutdown context, and record that we're waiting for it.
821 RefPtr<nsThreadShutdownContext> context =
822 new nsThreadShutdownContext(WrapNotNull(this), currentThread);
824 ++currentThread->mOutstandingShutdownContexts;
825 nsCOMPtr<nsIRunnable> clearOutstanding = NS_NewRunnableFunction(
826 "nsThread::ClearOutstandingShutdownContext",
827 [currentThread] { --currentThread->mOutstandingShutdownContexts; });
828 context->OnCompletion(clearOutstanding);
830 // Set mShutdownContext and wake up the thread in case it is waiting for
831 // events to process.
832 nsCOMPtr<nsIRunnable> event =
833 new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context));
834 if (!mEvents->PutEvent(event.forget(), EventQueuePriority::Normal)) {
835 // We do not expect this to happen. Let's collect some diagnostics.
836 nsAutoCString threadName;
837 GetThreadName(threadName);
838 MOZ_CRASH_UNSAFE_PRINTF("Attempt to shutdown an already dead thread: %s",
839 threadName.get());
842 // We could still end up with other events being added after the shutdown
843 // task, but that's okay because we process pending events in ThreadFunc
844 // after setting mShutdownContext just before exiting.
845 context.forget(aShutdown);
846 return NS_OK;
849 void nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext) {
850 MOZ_ASSERT(mEvents);
851 MOZ_ASSERT(mEventTarget);
852 MOZ_ASSERT(aContext->mTerminatingThread == this);
854 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
856 MutexAutoLock lock(aContext->mJoiningThreadMutex);
858 // StopWaitingAndLeakThread is explicitely meant to not cause a
859 // nsThreadShutdownAckEvent on the joining thread, which is the only
860 // caller of ShutdownComplete.
861 MOZ_DIAGNOSTIC_ASSERT(!aContext->mThreadLeaked);
863 #endif
865 MaybeRemoveFromThreadList();
867 // Now, it should be safe to join without fear of dead-locking.
868 PR_JoinThread(aContext->mTerminatingPRThread);
869 MOZ_ASSERT(!mThread);
871 #ifdef DEBUG
872 nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
873 MOZ_ASSERT(!obs, "Should have been cleared at shutdown!");
874 #endif
876 aContext->MarkCompleted();
879 void nsThread::WaitForAllAsynchronousShutdowns() {
880 // This is the motivating example for why SpinEventLoopUntil
881 // has the template parameter we are providing here.
882 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
883 "nsThread::WaitForAllAsynchronousShutdowns"_ns,
884 [&]() { return mOutstandingShutdownContexts == 0; }, this);
887 NS_IMETHODIMP
888 nsThread::Shutdown() {
889 LOG(("THRD(%p) sync shutdown\n", this));
891 nsCOMPtr<nsIThreadShutdown> context;
892 nsresult rv = BeginShutdown(getter_AddRefs(context));
893 if (NS_FAILED(rv)) {
894 return NS_OK; // The thread has already shut down.
897 // If we are going to hang here we want to see the thread's name
898 nsAutoCString threadName;
899 GetThreadName(threadName);
901 // Process events on the current thread until we receive a shutdown ACK.
902 // Allows waiting; ensure no locks are held that would deadlock us!
903 SpinEventLoopUntil("nsThread::Shutdown: "_ns + threadName,
904 [&]() { return context->GetCompleted(); });
906 return NS_OK;
909 NS_IMETHODIMP
910 nsThread::HasPendingEvents(bool* aResult) {
911 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
912 return NS_ERROR_NOT_SAME_THREAD;
915 if (mIsMainThread) {
916 *aResult = TaskController::Get()->HasMainThreadPendingTasks();
917 } else {
918 *aResult = mEvents->HasPendingEvent();
920 return NS_OK;
923 NS_IMETHODIMP
924 nsThread::HasPendingHighPriorityEvents(bool* aResult) {
925 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
926 return NS_ERROR_NOT_SAME_THREAD;
929 // This function appears to never be called anymore.
930 *aResult = false;
931 return NS_OK;
934 NS_IMETHODIMP
935 nsThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
936 EventQueuePriority aQueue) {
937 nsCOMPtr<nsIRunnable> event = aEvent;
939 if (NS_WARN_IF(!event)) {
940 return NS_ERROR_INVALID_ARG;
943 if (!mEvents->PutEvent(event.forget(), aQueue)) {
944 NS_WARNING(
945 "An idle event was posted to a thread that will never run it "
946 "(rejected)");
947 return NS_ERROR_UNEXPECTED;
950 return NS_OK;
953 NS_IMETHODIMP nsThread::SetThreadQoS(nsIThread::QoSPriority aPriority) {
954 if (!StaticPrefs::threads_use_low_power_enabled()) {
955 return NS_OK;
957 // The approach here is to have a thread set itself for its QoS level,
958 // so we assert if we aren't on the current thread.
959 MOZ_ASSERT(IsOnCurrentThread(), "Can only change the current thread's QoS");
961 #if defined(XP_MACOSX)
962 // Only arm64 macs may possess heterogeneous cores. On these, we can tell
963 // a thread to set its own QoS status. On intel macs things should behave
964 // normally, and the OS will ignore the QoS state of the thread.
965 if (aPriority == nsIThread::QOS_PRIORITY_LOW) {
966 pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0);
967 } else if (NS_IsMainThread()) {
968 // MacOS documentation specifies that a main thread should be initialized at
969 // the USER_INTERACTIVE priority, so when we restore thread priorities the
970 // main thread should be setting itself to this.
971 pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
972 } else {
973 pthread_set_qos_class_self_np(QOS_CLASS_DEFAULT, 0);
975 #endif
976 // Do nothing if an OS-specific implementation is unavailable.
977 return NS_OK;
980 #ifdef MOZ_CANARY
981 void canary_alarm_handler(int signum);
983 class Canary {
984 // XXX ToDo: support nested loops
985 public:
986 Canary() {
987 if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) {
988 signal(SIGALRM, canary_alarm_handler);
989 ualarm(15000, 0);
993 ~Canary() {
994 if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) {
995 ualarm(0, 0);
999 static bool EventLatencyIsImportant() {
1000 return NS_IsMainThread() && XRE_IsParentProcess();
1004 void canary_alarm_handler(int signum) {
1005 void* array[30];
1006 const char msg[29] = "event took too long to run:\n";
1007 // use write to be safe in the signal handler
1008 write(sCanaryOutputFD, msg, sizeof(msg));
1009 backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD);
1012 #endif
1014 #define NOTIFY_EVENT_OBSERVERS(observers_, func_, params_) \
1015 do { \
1016 if (!observers_.IsEmpty()) { \
1017 for (nsCOMPtr<nsIThreadObserver> obs_ : observers_.ForwardRange()) { \
1018 obs_->func_ params_; \
1021 } while (0)
1023 size_t nsThread::ShallowSizeOfIncludingThis(
1024 mozilla::MallocSizeOf aMallocSizeOf) const {
1025 size_t n = 0;
1026 if (mShutdownContext) {
1027 n += aMallocSizeOf(mShutdownContext);
1029 return aMallocSizeOf(this) + aMallocSizeOf(mThread) + n;
1032 size_t nsThread::SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const {
1033 size_t n = 0;
1034 if (mEventTarget) {
1035 // The size of mEvents is reported by mEventTarget.
1036 n += mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
1038 return n;
1041 size_t nsThread::SizeOfIncludingThis(
1042 mozilla::MallocSizeOf aMallocSizeOf) const {
1043 return ShallowSizeOfIncludingThis(aMallocSizeOf) +
1044 SizeOfEventQueues(aMallocSizeOf);
1047 NS_IMETHODIMP
1048 nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) {
1049 MOZ_ASSERT(mEvents);
1050 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1052 LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
1053 mNestedEventLoopDepth));
1055 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1056 return NS_ERROR_NOT_SAME_THREAD;
1059 // The toplevel event loop normally blocks waiting for the next event, but
1060 // if we're trying to shut this thread down, we must exit the event loop
1061 // when the event queue is empty. This only applys to the toplevel event
1062 // loop! Nested event loops (e.g. during sync dispatch) are waiting for
1063 // some state change and must be able to block even if something has
1064 // requested shutdown of the thread. Otherwise we'll just busywait as we
1065 // endlessly look for an event, fail to find one, and repeat the nested
1066 // event loop since its state change hasn't happened yet.
1067 bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
1069 Maybe<dom::AutoNoJSAPI> noJSAPI;
1071 if (mUseHangMonitor && reallyWait) {
1072 BackgroundHangMonitor().NotifyWait();
1075 if (mIsMainThread) {
1076 DoMainThreadSpecificProcessing();
1079 ++mNestedEventLoopDepth;
1081 // We only want to create an AutoNoJSAPI on threads that actually do DOM
1082 // stuff (including workers). Those are exactly the threads that have an
1083 // mScriptObserver.
1084 bool callScriptObserver = !!mScriptObserver;
1085 if (callScriptObserver) {
1086 noJSAPI.emplace();
1087 mScriptObserver->BeforeProcessTask(reallyWait);
1090 DrainDirectTasks();
1092 nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserverOnThread();
1093 if (obs) {
1094 obs->OnProcessNextEvent(this, reallyWait);
1097 NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), OnProcessNextEvent,
1098 (this, reallyWait));
1100 DrainDirectTasks();
1102 #ifdef MOZ_CANARY
1103 Canary canary;
1104 #endif
1105 nsresult rv = NS_OK;
1108 // Scope for |event| to make sure that its destructor fires while
1109 // mNestedEventLoopDepth has been incremented, since that destructor can
1110 // also do work.
1111 nsCOMPtr<nsIRunnable> event;
1112 bool usingTaskController = mIsMainThread;
1113 if (usingTaskController) {
1114 event = TaskController::Get()->GetRunnableForMTTask(reallyWait);
1115 } else {
1116 event = mEvents->GetEvent(reallyWait, &mLastEventDelay);
1119 *aResult = (event.get() != nullptr);
1121 if (event) {
1122 LOG(("THRD(%p) running [%p]\n", this, event.get()));
1124 Maybe<LogRunnable::Run> log;
1126 if (!usingTaskController) {
1127 log.emplace(event);
1130 // Delay event processing to encourage whoever dispatched this event
1131 // to run.
1132 DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
1134 mozilla::TimeStamp now = mozilla::TimeStamp::Now();
1136 if (mUseHangMonitor) {
1137 BackgroundHangMonitor().NotifyActivity();
1140 Maybe<PerformanceCounterState::Snapshot> snapshot;
1141 if (!usingTaskController) {
1142 snapshot.emplace(mPerformanceCounterState.RunnableWillRun(now, false));
1145 mLastEventStart = now;
1147 if (!usingTaskController) {
1148 AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
1149 event->Run();
1150 } else {
1151 // Avoid generating "Runnable" profiler markers for the
1152 // "TaskController::ExecutePendingMTTasks" runnables created
1153 // by TaskController, which already adds "Runnable" markers
1154 // when executing tasks.
1155 event->Run();
1158 if (usingTaskController) {
1159 *aResult = TaskController::Get()->MTTaskRunnableProcessedTask();
1160 } else {
1161 mPerformanceCounterState.RunnableDidRun(EmptyCString(),
1162 std::move(snapshot.ref()));
1165 // To cover the event's destructor code inside the LogRunnable span.
1166 event = nullptr;
1167 } else {
1168 mLastEventDelay = TimeDuration();
1169 mLastEventStart = TimeStamp();
1170 if (aMayWait) {
1171 MOZ_ASSERT(ShuttingDown(),
1172 "This should only happen when shutting down");
1173 rv = NS_ERROR_UNEXPECTED;
1178 DrainDirectTasks();
1180 NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), AfterProcessNextEvent,
1181 (this, *aResult));
1183 if (obs) {
1184 obs->AfterProcessNextEvent(this, *aResult);
1187 // In case some EventObserver dispatched some direct tasks; process them
1188 // now.
1189 DrainDirectTasks();
1191 if (callScriptObserver) {
1192 if (mScriptObserver) {
1193 mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
1195 noJSAPI.reset();
1198 --mNestedEventLoopDepth;
1200 return rv;
1203 //-----------------------------------------------------------------------------
1204 // nsISupportsPriority
1206 NS_IMETHODIMP
1207 nsThread::GetPriority(int32_t* aPriority) {
1208 *aPriority = mPriority;
1209 return NS_OK;
1212 NS_IMETHODIMP
1213 nsThread::SetPriority(int32_t aPriority) {
1214 if (NS_WARN_IF(!mThread)) {
1215 return NS_ERROR_NOT_INITIALIZED;
1218 // NSPR defines the following four thread priorities:
1219 // PR_PRIORITY_LOW
1220 // PR_PRIORITY_NORMAL
1221 // PR_PRIORITY_HIGH
1222 // PR_PRIORITY_URGENT
1223 // We map the priority values defined on nsISupportsPriority to these
1224 // values.
1226 mPriority = aPriority;
1228 PRThreadPriority pri;
1229 if (mPriority <= PRIORITY_HIGHEST) {
1230 pri = PR_PRIORITY_URGENT;
1231 } else if (mPriority < PRIORITY_NORMAL) {
1232 pri = PR_PRIORITY_HIGH;
1233 } else if (mPriority > PRIORITY_NORMAL) {
1234 pri = PR_PRIORITY_LOW;
1235 } else {
1236 pri = PR_PRIORITY_NORMAL;
1238 // If chaos mode is active, retain the randomly chosen priority
1239 if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
1240 PR_SetThreadPriority(mThread, pri);
1243 return NS_OK;
1246 NS_IMETHODIMP
1247 nsThread::AdjustPriority(int32_t aDelta) {
1248 return SetPriority(mPriority + aDelta);
1251 //-----------------------------------------------------------------------------
1252 // nsIThreadInternal
1254 NS_IMETHODIMP
1255 nsThread::GetObserver(nsIThreadObserver** aObs) {
1256 MOZ_ASSERT(mEvents);
1257 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1259 nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
1260 obs.forget(aObs);
1261 return NS_OK;
1264 NS_IMETHODIMP
1265 nsThread::SetObserver(nsIThreadObserver* aObs) {
1266 MOZ_ASSERT(mEvents);
1267 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1269 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1270 return NS_ERROR_NOT_SAME_THREAD;
1273 mEvents->SetObserver(aObs);
1274 return NS_OK;
1277 uint32_t nsThread::RecursionDepth() const {
1278 MOZ_ASSERT(PR_GetCurrentThread() == mThread);
1279 return mNestedEventLoopDepth;
1282 NS_IMETHODIMP
1283 nsThread::AddObserver(nsIThreadObserver* aObserver) {
1284 MOZ_ASSERT(mEvents);
1285 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1287 if (NS_WARN_IF(!aObserver)) {
1288 return NS_ERROR_INVALID_ARG;
1290 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1291 return NS_ERROR_NOT_SAME_THREAD;
1294 EventQueue()->AddObserver(aObserver);
1296 return NS_OK;
1299 NS_IMETHODIMP
1300 nsThread::RemoveObserver(nsIThreadObserver* aObserver) {
1301 MOZ_ASSERT(mEvents);
1302 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1304 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1305 return NS_ERROR_NOT_SAME_THREAD;
1308 EventQueue()->RemoveObserver(aObserver);
1310 return NS_OK;
1313 void nsThread::SetScriptObserver(
1314 mozilla::CycleCollectedJSContext* aScriptObserver) {
1315 if (!aScriptObserver) {
1316 mScriptObserver = nullptr;
1317 return;
1320 MOZ_ASSERT(!mScriptObserver);
1321 mScriptObserver = aScriptObserver;
1324 void NS_DispatchMemoryPressure();
1326 void nsThread::DoMainThreadSpecificProcessing() const {
1327 MOZ_ASSERT(mIsMainThread);
1329 ipc::CancelCPOWs();
1331 // Fire a memory pressure notification, if one is pending.
1332 if (!ShuttingDown()) {
1333 NS_DispatchMemoryPressure();
1337 //-----------------------------------------------------------------------------
1338 // nsIDirectTaskDispatcher
1340 NS_IMETHODIMP
1341 nsThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
1342 if (!IsOnCurrentThread()) {
1343 return NS_ERROR_FAILURE;
1345 mDirectTasks.AddTask(std::move(aEvent));
1346 return NS_OK;
1349 NS_IMETHODIMP nsThread::DrainDirectTasks() {
1350 if (!IsOnCurrentThread()) {
1351 return NS_ERROR_FAILURE;
1353 mDirectTasks.DrainTasks();
1354 return NS_OK;
1357 NS_IMETHODIMP nsThread::HaveDirectTasks(bool* aValue) {
1358 if (!IsOnCurrentThread()) {
1359 return NS_ERROR_FAILURE;
1362 *aValue = mDirectTasks.HaveTasks();
1363 return NS_OK;
1366 NS_IMPL_ISUPPORTS(nsThreadShutdownContext, nsIThreadShutdown)
1368 NS_IMETHODIMP
1369 nsThreadShutdownContext::OnCompletion(nsIRunnable* aEvent) {
1370 if (mCompleted) {
1371 aEvent->Run();
1372 } else {
1373 mCompletionCallbacks.AppendElement(aEvent);
1375 return NS_OK;
1378 NS_IMETHODIMP
1379 nsThreadShutdownContext::GetCompleted(bool* aCompleted) {
1380 *aCompleted = mCompleted;
1381 return NS_OK;
1384 NS_IMETHODIMP
1385 nsThreadShutdownContext::StopWaitingAndLeakThread() {
1386 // Take the joining thread from `mJoiningThread` so that the terminating
1387 // thread won't try to dispatch nsThreadShutdownAckEvent to us anymore.
1388 RefPtr<nsThread> joiningThread;
1390 MutexAutoLock lock(mJoiningThreadMutex);
1391 if (!mJoiningThread) {
1392 // Shutdown is already being resolved, so there's nothing for us to do.
1393 return NS_ERROR_NOT_AVAILABLE;
1395 joiningThread = mJoiningThread.forget();
1396 mThreadLeaked = true;
1399 MOZ_DIAGNOSTIC_ASSERT(joiningThread->IsOnCurrentThread());
1401 MarkCompleted();
1403 return NS_OK;
1406 void nsThreadShutdownContext::MarkCompleted() {
1407 MOZ_ASSERT(!mCompleted);
1408 mCompleted = true;
1409 nsTArray<nsCOMPtr<nsIRunnable>> callbacks(std::move(mCompletionCallbacks));
1410 for (auto& callback : callbacks) {
1411 callback->Run();
1415 namespace mozilla {
1416 PerformanceCounterState::Snapshot PerformanceCounterState::RunnableWillRun(
1417 TimeStamp aNow, bool aIsIdleRunnable) {
1418 if (mIsMainThread && IsNestedRunnable()) {
1419 // Flush out any accumulated time that should be accounted to the
1420 // current runnable before we start running a nested runnable. Don't
1421 // do this for non-mainthread threads that may be running their own
1422 // event loops, like SocketThread.
1423 MaybeReportAccumulatedTime("nested runnable"_ns, aNow);
1426 Snapshot snapshot(mCurrentEventLoopDepth, mCurrentRunnableIsIdleRunnable);
1428 mCurrentEventLoopDepth = mNestedEventLoopDepth;
1429 mCurrentRunnableIsIdleRunnable = aIsIdleRunnable;
1430 mCurrentTimeSliceStart = aNow;
1432 return snapshot;
1435 void PerformanceCounterState::RunnableDidRun(const nsCString& aName,
1436 Snapshot&& aSnapshot) {
1437 // First thing: Restore our mCurrentEventLoopDepth so we can use
1438 // IsNestedRunnable().
1439 mCurrentEventLoopDepth = aSnapshot.mOldEventLoopDepth;
1441 // We may not need the current timestamp; don't bother computing it if we
1442 // don't.
1443 TimeStamp now;
1444 if (mLongTaskLength.isSome() || IsNestedRunnable()) {
1445 now = TimeStamp::Now();
1447 if (mLongTaskLength.isSome()) {
1448 MaybeReportAccumulatedTime(aName, now);
1451 // And now restore the rest of our state.
1452 mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable;
1453 if (IsNestedRunnable()) {
1454 // Reset mCurrentTimeSliceStart to right now, so our parent runnable's
1455 // next slice can be properly accounted for.
1456 mCurrentTimeSliceStart = now;
1457 } else {
1458 // We are done at the outermost level; we are no longer in a timeslice.
1459 mCurrentTimeSliceStart = TimeStamp();
1463 void PerformanceCounterState::MaybeReportAccumulatedTime(const nsCString& aName,
1464 TimeStamp aNow) {
1465 MOZ_ASSERT(mCurrentTimeSliceStart,
1466 "How did we get here if we're not in a timeslice?");
1467 if (!mLongTaskLength.isSome()) {
1468 return;
1471 TimeDuration duration = aNow - mCurrentTimeSliceStart;
1472 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
1473 if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_TELEMETRY_MS) {
1474 Telemetry::Accumulate(Telemetry::EVENT_LONGTASK, aName,
1475 duration.ToMilliseconds());
1477 #endif
1479 // Long tasks only matter on the main thread.
1480 if (duration.ToMilliseconds() >= mLongTaskLength.value()) {
1481 // Idle events (gc...) don't *really* count here
1482 if (!mCurrentRunnableIsIdleRunnable) {
1483 mLastLongNonIdleTaskEnd = aNow;
1485 mLastLongTaskEnd = aNow;
1487 if (profiler_thread_is_being_profiled_for_markers()) {
1488 struct LongTaskMarker {
1489 static constexpr Span<const char> MarkerTypeName() {
1490 return MakeStringSpan("MainThreadLongTask");
1492 static void StreamJSONMarkerData(
1493 baseprofiler::SpliceableJSONWriter& aWriter) {
1494 aWriter.StringProperty("category", "LongTask");
1496 static MarkerSchema MarkerTypeDisplay() {
1497 using MS = MarkerSchema;
1498 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
1499 schema.AddKeyLabelFormatSearchable("category", "Type",
1500 MS::Format::String,
1501 MS::Searchable::Searchable);
1502 return schema;
1506 profiler_add_marker(mCurrentRunnableIsIdleRunnable
1507 ? ProfilerString8View("LongIdleTask")
1508 : ProfilerString8View("LongTask"),
1509 geckoprofiler::category::OTHER,
1510 MarkerTiming::Interval(mCurrentTimeSliceStart, aNow),
1511 LongTaskMarker{});
1516 } // namespace mozilla