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 "nsThread.h"
8
9 #include "base/message_loop.h"
10 #include "base/platform_thread.h"
11
12 // Chromium's logging can sometimes leak through...
13 #ifdef LOG
14 # undef LOG
15 #endif
16
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 "InputEventStatistics.h"
51 #include "ThreadEventQueue.h"
52 #include "ThreadEventTarget.h"
53 #include "ThreadDelay.h"
54
55 #include <limits>
56
57 #ifdef XP_LINUX
58 # ifdef __GLIBC__
59 # include <gnu/libc-version.h>
60 # endif
61 # include <sys/mman.h>
62 # include <sys/time.h>
63 # include <sys/resource.h>
64 # include <sched.h>
65 # include <stdio.h>
66 #endif
67
68 #ifdef XP_WIN
69 # include "mozilla/DynamicallyLinkedFunctionPtr.h"
70
71 # include <winbase.h>
72
73 using GetCurrentThreadStackLimitsFn = void(WINAPI*)(PULONG_PTR LowLimit,
74 PULONG_PTR HighLimit);
75 #endif
76
77 #define HAVE_UALARM \
78 _BSD_SOURCE || \
79 (_XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
80 !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
81
82 #if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE)
83 # define HAVE_SCHED_SETAFFINITY
84 #endif
85
86 #ifdef XP_MACOSX
87 # include <mach/mach.h>
88 # include <mach/thread_policy.h>
89 #endif
90
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
98
99 using namespace mozilla;
100
101 extern void InitThreadLocalVariables();
102
103 static LazyLogModule sThreadLog("nsThread");
104 #ifdef LOG
105 # undef LOG
106 #endif
107 #define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args)
108
109 NS_DECL_CI_INTERFACE_GETTER(nsThread)
110
111 Array<char, nsThread::kRunnableNameBufSize> nsThread::sMainThreadRunnableName;
112
113 #ifdef EARLY_BETA_OR_EARLIER
114 const uint32_t kTelemetryWakeupCountLimit = 100;
115 #endif
116
117 //-----------------------------------------------------------------------------
118 // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
119 // somewhat manually.
120
121 class nsThreadClassInfo : public nsIClassInfo {
122 public:
123 NS_DECL_ISUPPORTS_INHERITED // no mRefCnt
124 NS_DECL_NSICLASSINFO
125
126 nsThreadClassInfo() = default;
127 };
128
NS_IMETHODIMP_(MozExternalRefCountType)129 NS_IMETHODIMP_(MozExternalRefCountType)
130 nsThreadClassInfo::AddRef() { return 2; }
NS_IMETHODIMP_(MozExternalRefCountType)131 NS_IMETHODIMP_(MozExternalRefCountType)
132 nsThreadClassInfo::Release() { return 1; }
NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo,nsIClassInfo)133 NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo)
134
135 NS_IMETHODIMP
136 nsThreadClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) {
137 return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aArray);
138 }
139
140 NS_IMETHODIMP
GetScriptableHelper(nsIXPCScriptable ** aResult)141 nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) {
142 *aResult = nullptr;
143 return NS_OK;
144 }
145
146 NS_IMETHODIMP
GetContractID(nsACString & aResult)147 nsThreadClassInfo::GetContractID(nsACString& aResult) {
148 aResult.SetIsVoid(true);
149 return NS_OK;
150 }
151
152 NS_IMETHODIMP
GetClassDescription(nsACString & aResult)153 nsThreadClassInfo::GetClassDescription(nsACString& aResult) {
154 aResult.SetIsVoid(true);
155 return NS_OK;
156 }
157
158 NS_IMETHODIMP
GetClassID(nsCID ** aResult)159 nsThreadClassInfo::GetClassID(nsCID** aResult) {
160 *aResult = nullptr;
161 return NS_OK;
162 }
163
164 NS_IMETHODIMP
GetFlags(uint32_t * aResult)165 nsThreadClassInfo::GetFlags(uint32_t* aResult) {
166 *aResult = THREADSAFE;
167 return NS_OK;
168 }
169
170 NS_IMETHODIMP
GetClassIDNoAlloc(nsCID * aResult)171 nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) {
172 return NS_ERROR_NOT_AVAILABLE;
173 }
174
175 //-----------------------------------------------------------------------------
176
177 NS_IMPL_ADDREF(nsThread)
178 NS_IMPL_RELEASE(nsThread)
179 NS_INTERFACE_MAP_BEGIN(nsThread)
180 NS_INTERFACE_MAP_ENTRY(nsIThread)
181 NS_INTERFACE_MAP_ENTRY(nsIThreadInternal)
182 NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
183 NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
184 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
185 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDelayedRunnableObserver, mEventTarget)
186 NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher)
187 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
188 if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
189 static nsThreadClassInfo sThreadClassInfo;
190 foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo);
191 } else
192 NS_INTERFACE_MAP_END
193 NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal,
194 nsIEventTarget, nsISerialEventTarget,
195 nsISupportsPriority)
196
197 //-----------------------------------------------------------------------------
198
199 // This event is responsible for notifying nsThread::Shutdown that it is time
200 // to call PR_JoinThread. It implements nsICancelableRunnable so that it can
201 // run on a DOM Worker thread (where all events must implement
202 // nsICancelableRunnable.)
203 class nsThreadShutdownAckEvent : public CancelableRunnable {
204 public:
nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext * > aCtx)205 explicit nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext*> aCtx)
206 : CancelableRunnable("nsThreadShutdownAckEvent"),
207 mShutdownContext(aCtx) {}
Run()208 NS_IMETHOD Run() override {
209 mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext);
210 return NS_OK;
211 }
Cancel()212 nsresult Cancel() override { return Run(); }
213
214 private:
215 virtual ~nsThreadShutdownAckEvent() = default;
216
217 NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext;
218 };
219
220 // This event is responsible for setting mShutdownContext
221 class nsThreadShutdownEvent : public Runnable {
222 public:
nsThreadShutdownEvent(NotNull<nsThread * > aThr,NotNull<nsThreadShutdownContext * > aCtx)223 nsThreadShutdownEvent(NotNull<nsThread*> aThr,
224 NotNull<nsThreadShutdownContext*> aCtx)
225 : Runnable("nsThreadShutdownEvent"),
226 mThread(aThr),
227 mShutdownContext(aCtx) {}
Run()228 NS_IMETHOD Run() override {
229 // Creates a cycle between `mThread` and the shutdown context which will be
230 // broken when the thread exits.
231 mThread->mShutdownContext = mShutdownContext;
232 if (mThread->mEventTarget) {
233 mThread->mEventTarget->NotifyShutdown();
234 }
235 MessageLoop::current()->Quit();
236 return NS_OK;
237 }
238
239 private:
240 NotNull<RefPtr<nsThread>> mThread;
241 NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext;
242 };
243
244 //-----------------------------------------------------------------------------
245
SetThreadAffinity(unsigned int cpu)246 static void SetThreadAffinity(unsigned int cpu) {
247 #ifdef HAVE_SCHED_SETAFFINITY
248 cpu_set_t cpus;
249 CPU_ZERO(&cpus);
250 CPU_SET(cpu, &cpus);
251 sched_setaffinity(0, sizeof(cpus), &cpus);
252 // Don't assert sched_setaffinity's return value because it intermittently (?)
253 // fails with EINVAL on Linux x64 try runs.
254 #elif defined(XP_MACOSX)
255 // OS X does not provide APIs to pin threads to specific processors, but you
256 // can tag threads as belonging to the same "affinity set" and the OS will try
257 // to run them on the same processor. To run threads on different processors,
258 // tag them as belonging to different affinity sets. Tag 0, the default, means
259 // "no affinity" so let's pretend each CPU has its own tag `cpu+1`.
260 thread_affinity_policy_data_t policy;
261 policy.affinity_tag = cpu + 1;
262 kern_return_t kr = thread_policy_set(
263 mach_thread_self(), THREAD_AFFINITY_POLICY, &policy.affinity_tag, 1);
264 // Setting the thread affinity is not supported on ARM.
265 MOZ_ALWAYS_TRUE(kr == KERN_SUCCESS || kr == KERN_NOT_SUPPORTED);
266 #elif defined(XP_WIN)
267 MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) !=
268 (DWORD)-1);
269 #endif
270 }
271
SetupCurrentThreadForChaosMode()272 static void SetupCurrentThreadForChaosMode() {
273 if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
274 return;
275 }
276
277 #ifdef XP_LINUX
278 // PR_SetThreadPriority doesn't really work since priorities >
279 // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use
280 // setpriority(2) to set random 'nice values'. In regular Linux this is only
281 // a dynamic adjustment so it still doesn't really do what we want, but tools
282 // like 'rr' can be more aggressive about honoring these values.
283 // Some of these calls may fail due to trying to lower the priority
284 // (e.g. something may have already called setpriority() for this thread).
285 // This makes it hard to have non-main threads with higher priority than the
286 // main thread, but that's hard to fix. Tools like rr can choose to honor the
287 // requested values anyway.
288 // Use just 4 priorities so there's a reasonable chance of any two threads
289 // having equal priority.
290 setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4));
291 #else
292 // We should set the affinity here but NSPR doesn't provide a way to expose
293 // it.
294 uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1);
295 PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority));
296 #endif
297
298 // Force half the threads to CPU 0 so they compete for CPU
299 if (ChaosMode::randomUint32LessThan(2)) {
300 SetThreadAffinity(0);
301 }
302 }
303
304 namespace {
305
306 struct ThreadInitData {
307 nsThread* thread;
308 nsCString name;
309 };
310
311 } // namespace
312
ThreadListMutex()313 /* static */ mozilla::OffTheBooksMutex& nsThread::ThreadListMutex() {
314 static StaticLocalAutoPtr<OffTheBooksMutex> sMutex(
315 new OffTheBooksMutex("nsThread::ThreadListMutex"));
316 return *sMutex;
317 }
318
ThreadList()319 /* static */ LinkedList<nsThread>& nsThread::ThreadList() {
320 static StaticLocalAutoPtr<LinkedList<nsThread>> sList(
321 new LinkedList<nsThread>());
322 return *sList;
323 }
324
325 /* static */
ClearThreadList()326 void nsThread::ClearThreadList() {
327 OffTheBooksMutexAutoLock mal(ThreadListMutex());
328 while (ThreadList().popFirst()) {
329 }
330 }
331
332 /* static */
Enumerate()333 nsThreadEnumerator nsThread::Enumerate() { return {}; }
334
AddToThreadList()335 void nsThread::AddToThreadList() {
336 OffTheBooksMutexAutoLock mal(ThreadListMutex());
337 MOZ_ASSERT(!isInList());
338
339 ThreadList().insertBack(this);
340 }
341
MaybeRemoveFromThreadList()342 void nsThread::MaybeRemoveFromThreadList() {
343 OffTheBooksMutexAutoLock mal(ThreadListMutex());
344 if (isInList()) {
345 removeFrom(ThreadList());
346 }
347 }
348
349 /*static*/
ThreadFunc(void * aArg)350 void nsThread::ThreadFunc(void* aArg) {
351 using mozilla::ipc::BackgroundChild;
352
353 UniquePtr<ThreadInitData> initData(static_cast<ThreadInitData*>(aArg));
354 nsThread* self = initData->thread; // strong reference
355
356 MOZ_ASSERT(self->mEventTarget);
357 MOZ_ASSERT(self->mEvents);
358
359 // Note: see the comment in nsThread::Init, where we set these same values.
360 DebugOnly<PRThread*> prev = self->mThread.exchange(PR_GetCurrentThread());
361 MOZ_ASSERT(!prev || prev == PR_GetCurrentThread());
362 self->mEventTarget->SetCurrentThread(self->mThread);
363 SetupCurrentThreadForChaosMode();
364
365 if (!initData->name.IsEmpty()) {
366 NS_SetCurrentThreadName(initData->name.BeginReading());
367 }
368
369 self->InitCommon();
370
371 // Inform the ThreadManager
372 nsThreadManager::get().RegisterCurrentThread(*self);
373
374 mozilla::IOInterposer::RegisterCurrentThread();
375
376 // This must come after the call to nsThreadManager::RegisterCurrentThread(),
377 // because that call is needed to properly set up this thread as an nsThread,
378 // which profiler_register_thread() requires. See bug 1347007.
379 const bool registerWithProfiler = !initData->name.IsEmpty();
380 if (registerWithProfiler) {
381 PROFILER_REGISTER_THREAD(initData->name.BeginReading());
382 }
383
384 {
385 // Scope for MessageLoop.
386 MessageLoop loop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, self);
387
388 // Now, process incoming events...
389 loop.Run();
390
391 BackgroundChild::CloseForCurrentThread();
392
393 // NB: The main thread does not shut down here! It shuts down via
394 // nsThreadManager::Shutdown.
395
396 // Do NS_ProcessPendingEvents but with special handling to set
397 // mEventsAreDoomed atomically with the removal of the last event. The key
398 // invariant here is that we will never permit PutEvent to succeed if the
399 // event would be left in the queue after our final call to
400 // NS_ProcessPendingEvents. We also have to keep processing events as long
401 // as we have outstanding mRequestedShutdownContexts.
402 while (true) {
403 // Check and see if we're waiting on any threads.
404 self->WaitForAllAsynchronousShutdowns();
405
406 if (self->mEvents->ShutdownIfNoPendingEvents()) {
407 break;
408 }
409 NS_ProcessPendingEvents(self);
410 }
411 }
412
413 mozilla::IOInterposer::UnregisterCurrentThread();
414
415 // Inform the threadmanager that this thread is going away
416 nsThreadManager::get().UnregisterCurrentThread(*self);
417
418 // The thread should only unregister itself if it was registered above.
419 if (registerWithProfiler) {
420 PROFILER_UNREGISTER_THREAD();
421 }
422
423 NotNull<RefPtr<nsThreadShutdownContext>> context =
424 WrapNotNull(self->mShutdownContext);
425 self->mShutdownContext = nullptr;
426 MOZ_ASSERT(context->mTerminatingThread == self);
427
428 // Take the joining thread from our shutdown context. This may have been
429 // cleared by the joining thread if it decided to cancel waiting on us, in
430 // which case we won't notify our caller, and leak.
431 RefPtr<nsThread> joiningThread;
432 {
433 auto lock = context->mJoiningThread.Lock();
434 joiningThread = lock->forget();
435 }
436 if (joiningThread) {
437 // Dispatch shutdown ACK
438 nsCOMPtr<nsIRunnable> event = new nsThreadShutdownAckEvent(context);
439 nsresult dispatch_ack_rv =
440 joiningThread->Dispatch(event, NS_DISPATCH_NORMAL);
441
442 // We do not expect this to ever happen, but If we cannot dispatch
443 // the ack event, someone probably blocks waiting on us and will
444 // crash with a hang later anyways. The best we can do is to tell
445 // the world what happened right here.
446 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(dispatch_ack_rv));
447 } else {
448 NS_WARNING(
449 "nsThread exiting after StopWaitingAndLeakThread was called, thread "
450 "resources will be leaked!");
451 }
452
453 // Release any observer of the thread here.
454 self->SetObserver(nullptr);
455
456 // The PRThread will be deleted in PR_JoinThread(), so clear references.
457 self->mThread = nullptr;
458 self->mEventTarget->ClearCurrentThread();
459 NS_RELEASE(self);
460 }
461
InitCommon()462 void nsThread::InitCommon() {
463 mThreadId = uint32_t(PlatformThread::CurrentId());
464
465 {
466 #if defined(XP_LINUX)
467 pthread_attr_t attr;
468 pthread_attr_init(&attr);
469 pthread_getattr_np(pthread_self(), &attr);
470
471 size_t stackSize;
472 pthread_attr_getstack(&attr, &mStackBase, &stackSize);
473
474 // Glibc prior to 2.27 reports the stack size and base including the guard
475 // region, so we need to compensate for it to get accurate accounting.
476 // Also, this behavior difference isn't guarded by a versioned symbol, so we
477 // actually need to check the runtime glibc version, not the version we were
478 // compiled against.
479 static bool sAdjustForGuardSize = ({
480 # ifdef __GLIBC__
481 unsigned major, minor;
482 sscanf(gnu_get_libc_version(), "%u.%u", &major, &minor) < 2 ||
483 major < 2 || (major == 2 && minor < 27);
484 # else
485 false;
486 # endif
487 });
488 if (sAdjustForGuardSize) {
489 size_t guardSize;
490 pthread_attr_getguardsize(&attr, &guardSize);
491
492 // Note: This assumes that the stack grows down, as is the case on all of
493 // our tier 1 platforms. On platforms where the stack grows up, the
494 // mStackBase adjustment is unnecessary, but doesn't cause any harm other
495 // than under-counting stack memory usage by one page.
496 mStackBase = reinterpret_cast<char*>(mStackBase) + guardSize;
497 stackSize -= guardSize;
498 }
499
500 mStackSize = stackSize;
501
502 // This is a bit of a hack.
503 //
504 // We really do want the NOHUGEPAGE flag on our thread stacks, since we
505 // don't expect any of them to need anywhere near 2MB of space. But setting
506 // it here is too late to have an effect, since the first stack page has
507 // already been faulted in existence, and NSPR doesn't give us a way to set
508 // it beforehand.
509 //
510 // What this does get us, however, is a different set of VM flags on our
511 // thread stacks compared to normal heap memory. Which makes the Linux
512 // kernel report them as separate regions, even when they are adjacent to
513 // heap memory. This allows us to accurately track the actual memory
514 // consumption of our allocated stacks.
515 madvise(mStackBase, stackSize, MADV_NOHUGEPAGE);
516
517 pthread_attr_destroy(&attr);
518 #elif defined(XP_WIN)
519 static const StaticDynamicallyLinkedFunctionPtr<
520 GetCurrentThreadStackLimitsFn>
521 sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits");
522
523 if (sGetStackLimits) {
524 ULONG_PTR stackBottom, stackTop;
525 sGetStackLimits(&stackBottom, &stackTop);
526 mStackBase = reinterpret_cast<void*>(stackBottom);
527 mStackSize = stackTop - stackBottom;
528 }
529 #endif
530 }
531
532 InitThreadLocalVariables();
533 AddToThreadList();
534 }
535
536 //-----------------------------------------------------------------------------
537
538 #ifdef MOZ_CANARY
539 int sCanaryOutputFD = -1;
540 #endif
541
nsThread(NotNull<SynchronizedEventQueue * > aQueue,MainThreadFlag aMainThread,uint32_t aStackSize)542 nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue,
543 MainThreadFlag aMainThread, uint32_t aStackSize)
544 : mEvents(aQueue.get()),
545 mEventTarget(
546 new ThreadEventTarget(mEvents.get(), aMainThread == MAIN_THREAD)),
547 mOutstandingShutdownContexts(0),
548 mShutdownContext(nullptr),
549 mScriptObserver(nullptr),
550 mThreadName("<uninitialized>"),
551 mStackSize(aStackSize),
552 mNestedEventLoopDepth(0),
553 mShutdownRequired(false),
554 mPriority(PRIORITY_NORMAL),
555 mIsMainThread(aMainThread == MAIN_THREAD),
556 mUseHangMonitor(aMainThread == MAIN_THREAD),
557 mIsAPoolThreadFree(nullptr),
558 mCanInvokeJS(false),
559 #ifdef EARLY_BETA_OR_EARLIER
560 mLastWakeupCheckTime(TimeStamp::Now()),
561 #endif
562 mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) {
563 if (mIsMainThread) {
564 mozilla::TaskController::Get()->SetPerformanceCounterState(
565 &mPerformanceCounterState);
566 }
567 }
568
nsThread()569 nsThread::nsThread()
570 : mEvents(nullptr),
571 mEventTarget(nullptr),
572 mOutstandingShutdownContexts(0),
573 mShutdownContext(nullptr),
574 mScriptObserver(nullptr),
575 mThreadName("<uninitialized>"),
576 mStackSize(0),
577 mNestedEventLoopDepth(0),
578 mShutdownRequired(false),
579 mPriority(PRIORITY_NORMAL),
580 mIsMainThread(false),
581 mUseHangMonitor(false),
582 mCanInvokeJS(false),
583 #ifdef EARLY_BETA_OR_EARLIER
584 mLastWakeupCheckTime(TimeStamp::Now()),
585 #endif
586 mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) {
587 MOZ_ASSERT(!NS_IsMainThread());
588 }
589
~nsThread()590 nsThread::~nsThread() {
591 NS_ASSERTION(mOutstandingShutdownContexts == 0,
592 "shouldn't be waiting on other threads to shutdown");
593
594 MaybeRemoveFromThreadList();
595 }
596
Init(const nsACString & aName)597 nsresult nsThread::Init(const nsACString& aName) {
598 MOZ_ASSERT(mEvents);
599 MOZ_ASSERT(mEventTarget);
600 MOZ_ASSERT(!mThread);
601
602 NS_ADDREF_THIS();
603
604 SetThreadNameInternal(aName);
605
606 mShutdownRequired = true;
607
608 UniquePtr<ThreadInitData> initData(
609 new ThreadInitData{this, nsCString(aName)});
610
611 PRThread* thread = nullptr;
612 // ThreadFunc is responsible for setting mThread
613 if (!(thread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, initData.get(),
614 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
615 PR_JOINABLE_THREAD, mStackSize))) {
616 NS_RELEASE_THIS();
617 return NS_ERROR_OUT_OF_MEMORY;
618 }
619
620 // The created thread now owns initData, so release our ownership of it.
621 Unused << initData.release();
622
623 // Note: we set these both here and inside ThreadFunc, to what should be
624 // the same value. This is because calls within ThreadFunc need these values
625 // to be set, and our callers need these values to be set.
626 DebugOnly<PRThread*> prev = mThread.exchange(thread);
627 MOZ_ASSERT(!prev || prev == thread);
628
629 mEventTarget->SetCurrentThread(thread);
630 return NS_OK;
631 }
632
InitCurrentThread()633 nsresult nsThread::InitCurrentThread() {
634 mThread = PR_GetCurrentThread();
635 SetupCurrentThreadForChaosMode();
636 InitCommon();
637
638 nsThreadManager::get().RegisterCurrentThread(*this);
639 return NS_OK;
640 }
641
GetThreadName(nsACString & aNameBuffer)642 void nsThread::GetThreadName(nsACString& aNameBuffer) {
643 auto lock = mThreadName.Lock();
644 aNameBuffer = lock.ref();
645 }
646
SetThreadNameInternal(const nsACString & aName)647 void nsThread::SetThreadNameInternal(const nsACString& aName) {
648 auto lock = mThreadName.Lock();
649 lock->Assign(aName);
650 }
651
652 //-----------------------------------------------------------------------------
653 // nsIEventTarget
654
655 NS_IMETHODIMP
DispatchFromScript(nsIRunnable * aEvent,uint32_t aFlags)656 nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
657 MOZ_ASSERT(mEventTarget);
658 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
659
660 nsCOMPtr<nsIRunnable> event(aEvent);
661 return mEventTarget->Dispatch(event.forget(), aFlags);
662 }
663
664 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aFlags)665 nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
666 MOZ_ASSERT(mEventTarget);
667 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
668
669 LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */ nullptr, aFlags));
670
671 return mEventTarget->Dispatch(std::move(aEvent), aFlags);
672 }
673
674 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aDelayMs)675 nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
676 uint32_t aDelayMs) {
677 MOZ_ASSERT(mEventTarget);
678 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
679
680 return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs);
681 }
682
683 NS_IMETHODIMP
GetRunningEventDelay(TimeDuration * aDelay,TimeStamp * aStart)684 nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
685 if (mIsAPoolThreadFree && *mIsAPoolThreadFree) {
686 // if there are unstarted threads in the pool, a new event to the
687 // pool would not be delayed at all (beyond thread start time)
688 *aDelay = TimeDuration();
689 *aStart = TimeStamp();
690 } else {
691 *aDelay = mLastEventDelay;
692 *aStart = mLastEventStart;
693 }
694 return NS_OK;
695 }
696
697 NS_IMETHODIMP
SetRunningEventDelay(TimeDuration aDelay,TimeStamp aStart)698 nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
699 mLastEventDelay = aDelay;
700 mLastEventStart = aStart;
701 return NS_OK;
702 }
703
704 NS_IMETHODIMP
IsOnCurrentThread(bool * aResult)705 nsThread::IsOnCurrentThread(bool* aResult) {
706 if (mEventTarget) {
707 return mEventTarget->IsOnCurrentThread(aResult);
708 }
709 *aResult = PR_GetCurrentThread() == mThread;
710 return NS_OK;
711 }
712
NS_IMETHODIMP_(bool)713 NS_IMETHODIMP_(bool)
714 nsThread::IsOnCurrentThreadInfallible() {
715 // This method is only going to be called if `mThread` is null, which
716 // only happens when the thread has exited the event loop. Therefore, when
717 // we are called, we can never be on this thread.
718 return false;
719 }
720
721 //-----------------------------------------------------------------------------
722 // nsIThread
723
724 NS_IMETHODIMP
GetPRThread(PRThread ** aResult)725 nsThread::GetPRThread(PRThread** aResult) {
726 PRThread* thread = mThread; // atomic load
727 *aResult = thread;
728 return thread ? NS_OK : NS_ERROR_NOT_AVAILABLE;
729 }
730
731 NS_IMETHODIMP
GetCanInvokeJS(bool * aResult)732 nsThread::GetCanInvokeJS(bool* aResult) {
733 *aResult = mCanInvokeJS;
734 return NS_OK;
735 }
736
737 NS_IMETHODIMP
SetCanInvokeJS(bool aCanInvokeJS)738 nsThread::SetCanInvokeJS(bool aCanInvokeJS) {
739 mCanInvokeJS = aCanInvokeJS;
740 return NS_OK;
741 }
742
743 NS_IMETHODIMP
GetLastLongTaskEnd(TimeStamp * _retval)744 nsThread::GetLastLongTaskEnd(TimeStamp* _retval) {
745 *_retval = mPerformanceCounterState.LastLongTaskEnd();
746 return NS_OK;
747 }
748
749 NS_IMETHODIMP
GetLastLongNonIdleTaskEnd(TimeStamp * _retval)750 nsThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) {
751 *_retval = mPerformanceCounterState.LastLongNonIdleTaskEnd();
752 return NS_OK;
753 }
754
755 NS_IMETHODIMP
SetNameForWakeupTelemetry(const nsACString & aName)756 nsThread::SetNameForWakeupTelemetry(const nsACString& aName) {
757 #ifdef EARLY_BETA_OR_EARLIER
758 mNameForWakeupTelemetry = aName;
759 #endif
760 return NS_OK;
761 }
762
763 NS_IMETHODIMP
AsyncShutdown()764 nsThread::AsyncShutdown() {
765 LOG(("THRD(%p) async shutdown\n", this));
766
767 nsCOMPtr<nsIThreadShutdown> shutdown;
768 BeginShutdown(getter_AddRefs(shutdown));
769 return NS_OK;
770 }
771
772 NS_IMETHODIMP
BeginShutdown(nsIThreadShutdown ** aShutdown)773 nsThread::BeginShutdown(nsIThreadShutdown** aShutdown) {
774 LOG(("THRD(%p) begin shutdown\n", this));
775
776 MOZ_ASSERT(mEvents);
777 MOZ_ASSERT(mEventTarget);
778 MOZ_ASSERT(mThread != PR_GetCurrentThread());
779 if (NS_WARN_IF(mThread == PR_GetCurrentThread())) {
780 return NS_ERROR_UNEXPECTED;
781 }
782
783 // Prevent multiple calls to this method.
784 if (!mShutdownRequired.compareExchange(true, false)) {
785 return NS_ERROR_UNEXPECTED;
786 }
787 MOZ_ASSERT(mThread);
788
789 MaybeRemoveFromThreadList();
790
791 RefPtr<nsThread> currentThread = nsThreadManager::get().GetCurrentThread();
792
793 MOZ_DIAGNOSTIC_ASSERT(currentThread->EventQueue(),
794 "Shutdown() may only be called from an XPCOM thread");
795
796 // Allocate a shutdown context, and record that we're waiting for it.
797 RefPtr<nsThreadShutdownContext> context =
798 new nsThreadShutdownContext(WrapNotNull(this), currentThread);
799
800 ++currentThread->mOutstandingShutdownContexts;
801 nsCOMPtr<nsIRunnable> clearOutstanding = NS_NewRunnableFunction(
802 "nsThread::ClearOutstandingShutdownContext",
803 [currentThread] { --currentThread->mOutstandingShutdownContexts; });
804 context->OnCompletion(clearOutstanding);
805
806 // Set mShutdownContext and wake up the thread in case it is waiting for
807 // events to process.
808 nsCOMPtr<nsIRunnable> event =
809 new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context));
810 if (!mEvents->PutEvent(event.forget(), EventQueuePriority::Normal)) {
811 // We do not expect this to happen. Let's collect some diagnostics.
812 nsAutoCString threadName;
813 GetThreadName(threadName);
814 MOZ_CRASH_UNSAFE_PRINTF("Attempt to shutdown an already dead thread: %s",
815 threadName.get());
816 }
817
818 // We could still end up with other events being added after the shutdown
819 // task, but that's okay because we process pending events in ThreadFunc
820 // after setting mShutdownContext just before exiting.
821 context.forget(aShutdown);
822 return NS_OK;
823 }
824
ShutdownComplete(NotNull<nsThreadShutdownContext * > aContext)825 void nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext) {
826 MOZ_ASSERT(mEvents);
827 MOZ_ASSERT(mEventTarget);
828 MOZ_ASSERT(aContext->mTerminatingThread == this);
829
830 MaybeRemoveFromThreadList();
831
832 // Now, it should be safe to join without fear of dead-locking.
833 PR_JoinThread(aContext->mTerminatingPRThread);
834 MOZ_ASSERT(!mThread);
835
836 #ifdef DEBUG
837 nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
838 MOZ_ASSERT(!obs, "Should have been cleared at shutdown!");
839 #endif
840
841 aContext->MarkCompleted();
842 }
843
WaitForAllAsynchronousShutdowns()844 void nsThread::WaitForAllAsynchronousShutdowns() {
845 // This is the motivating example for why SpinEventLoopUntil
846 // has the template parameter we are providing here.
847 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
848 "nsThread::WaitForAllAsynchronousShutdowns"_ns,
849 [&]() { return mOutstandingShutdownContexts == 0; }, this);
850 }
851
852 NS_IMETHODIMP
Shutdown()853 nsThread::Shutdown() {
854 LOG(("THRD(%p) sync shutdown\n", this));
855
856 nsCOMPtr<nsIThreadShutdown> context;
857 nsresult rv = BeginShutdown(getter_AddRefs(context));
858 if (NS_FAILED(rv)) {
859 return NS_OK; // The thread has already shut down.
860 }
861
862 // If we are going to hang here we want to see the thread's name
863 nsAutoCString threadName;
864 GetThreadName(threadName);
865
866 // Process events on the current thread until we receive a shutdown ACK.
867 // Allows waiting; ensure no locks are held that would deadlock us!
868 SpinEventLoopUntil("nsThread::Shutdown: "_ns + threadName,
869 [&]() { return context->GetCompleted(); });
870
871 return NS_OK;
872 }
873
874 NS_IMETHODIMP
HasPendingEvents(bool * aResult)875 nsThread::HasPendingEvents(bool* aResult) {
876 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
877 return NS_ERROR_NOT_SAME_THREAD;
878 }
879
880 if (mIsMainThread && !mIsInLocalExecutionMode) {
881 *aResult = TaskController::Get()->HasMainThreadPendingTasks();
882 } else {
883 *aResult = mEvents->HasPendingEvent();
884 }
885 return NS_OK;
886 }
887
888 NS_IMETHODIMP
HasPendingHighPriorityEvents(bool * aResult)889 nsThread::HasPendingHighPriorityEvents(bool* aResult) {
890 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
891 return NS_ERROR_NOT_SAME_THREAD;
892 }
893
894 // This function appears to never be called anymore.
895 *aResult = false;
896 return NS_OK;
897 }
898
899 NS_IMETHODIMP
DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,EventQueuePriority aQueue)900 nsThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
901 EventQueuePriority aQueue) {
902 nsCOMPtr<nsIRunnable> event = aEvent;
903
904 if (NS_WARN_IF(!event)) {
905 return NS_ERROR_INVALID_ARG;
906 }
907
908 if (!mEvents->PutEvent(event.forget(), aQueue)) {
909 NS_WARNING(
910 "An idle event was posted to a thread that will never run it "
911 "(rejected)");
912 return NS_ERROR_UNEXPECTED;
913 }
914
915 return NS_OK;
916 }
917
918 #ifdef MOZ_CANARY
919 void canary_alarm_handler(int signum);
920
921 class Canary {
922 // XXX ToDo: support nested loops
923 public:
Canary()924 Canary() {
925 if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) {
926 signal(SIGALRM, canary_alarm_handler);
927 ualarm(15000, 0);
928 }
929 }
930
~Canary()931 ~Canary() {
932 if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) {
933 ualarm(0, 0);
934 }
935 }
936
EventLatencyIsImportant()937 static bool EventLatencyIsImportant() {
938 return NS_IsMainThread() && XRE_IsParentProcess();
939 }
940 };
941
canary_alarm_handler(int signum)942 void canary_alarm_handler(int signum) {
943 void* array[30];
944 const char msg[29] = "event took too long to run:\n";
945 // use write to be safe in the signal handler
946 write(sCanaryOutputFD, msg, sizeof(msg));
947 backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD);
948 }
949
950 #endif
951
952 #define NOTIFY_EVENT_OBSERVERS(observers_, func_, params_) \
953 do { \
954 if (!observers_.IsEmpty()) { \
955 for (nsCOMPtr<nsIThreadObserver> obs_ : observers_.ForwardRange()) { \
956 obs_->func_ params_; \
957 } \
958 } \
959 } while (0)
960
961 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
962 // static
GetLabeledRunnableName(nsIRunnable * aEvent,nsACString & aName,EventQueuePriority aPriority)963 bool nsThread::GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName,
964 EventQueuePriority aPriority) {
965 bool labeled = false;
966 if (RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent)) {
967 labeled = true;
968 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(groupRunnable->GetName(aName)));
969 } else if (nsCOMPtr<nsINamed> named = do_QueryInterface(aEvent)) {
970 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName)));
971 } else {
972 aName.AssignLiteral("non-nsINamed runnable");
973 }
974 if (aName.IsEmpty()) {
975 aName.AssignLiteral("anonymous runnable");
976 }
977
978 if (!labeled && aPriority > EventQueuePriority::InputHigh) {
979 aName.AppendLiteral("(unlabeled)");
980 }
981
982 return labeled;
983 }
984 #endif
985
GetPerformanceCounter(nsIRunnable * aEvent) const986 mozilla::PerformanceCounter* nsThread::GetPerformanceCounter(
987 nsIRunnable* aEvent) const {
988 return GetPerformanceCounterBase(aEvent);
989 }
990
991 // static
GetPerformanceCounterBase(nsIRunnable * aEvent)992 mozilla::PerformanceCounter* nsThread::GetPerformanceCounterBase(
993 nsIRunnable* aEvent) {
994 RefPtr<SchedulerGroup::Runnable> docRunnable = do_QueryObject(aEvent);
995 if (docRunnable) {
996 return docRunnable->GetPerformanceCounter();
997 }
998 return nullptr;
999 }
1000
ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const1001 size_t nsThread::ShallowSizeOfIncludingThis(
1002 mozilla::MallocSizeOf aMallocSizeOf) const {
1003 size_t n = 0;
1004 if (mShutdownContext) {
1005 n += aMallocSizeOf(mShutdownContext);
1006 }
1007 return aMallocSizeOf(this) + aMallocSizeOf(mThread) + n;
1008 }
1009
SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const1010 size_t nsThread::SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const {
1011 size_t n = 0;
1012 if (mEventTarget) {
1013 // The size of mEvents is reported by mEventTarget.
1014 n += mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
1015 }
1016 return n;
1017 }
1018
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const1019 size_t nsThread::SizeOfIncludingThis(
1020 mozilla::MallocSizeOf aMallocSizeOf) const {
1021 return ShallowSizeOfIncludingThis(aMallocSizeOf) +
1022 SizeOfEventQueues(aMallocSizeOf);
1023 }
1024
1025 NS_IMETHODIMP
ProcessNextEvent(bool aMayWait,bool * aResult)1026 nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) {
1027 MOZ_ASSERT(mEvents);
1028 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1029
1030 LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
1031 mNestedEventLoopDepth));
1032
1033 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1034 return NS_ERROR_NOT_SAME_THREAD;
1035 }
1036
1037 // The toplevel event loop normally blocks waiting for the next event, but
1038 // if we're trying to shut this thread down, we must exit the event loop
1039 // when the event queue is empty. This only applys to the toplevel event
1040 // loop! Nested event loops (e.g. during sync dispatch) are waiting for
1041 // some state change and must be able to block even if something has
1042 // requested shutdown of the thread. Otherwise we'll just busywait as we
1043 // endlessly look for an event, fail to find one, and repeat the nested
1044 // event loop since its state change hasn't happened yet.
1045 bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
1046
1047 if (mIsInLocalExecutionMode) {
1048 if (nsCOMPtr<nsIRunnable> event = mEvents->GetEvent(reallyWait)) {
1049 *aResult = true;
1050 LogRunnable::Run log(event);
1051 event->Run();
1052 event = nullptr;
1053 } else {
1054 *aResult = false;
1055 }
1056 return NS_OK;
1057 }
1058
1059 Maybe<dom::AutoNoJSAPI> noJSAPI;
1060
1061 if (mUseHangMonitor && reallyWait) {
1062 BackgroundHangMonitor().NotifyWait();
1063 }
1064
1065 if (mIsMainThread) {
1066 DoMainThreadSpecificProcessing();
1067 }
1068
1069 ++mNestedEventLoopDepth;
1070
1071 // We only want to create an AutoNoJSAPI on threads that actually do DOM
1072 // stuff (including workers). Those are exactly the threads that have an
1073 // mScriptObserver.
1074 bool callScriptObserver = !!mScriptObserver;
1075 if (callScriptObserver) {
1076 noJSAPI.emplace();
1077 mScriptObserver->BeforeProcessTask(reallyWait);
1078 }
1079
1080 #ifdef EARLY_BETA_OR_EARLIER
1081 // Need to capture mayWaitForWakeup state before OnProcessNextEvent,
1082 // since on the main thread OnProcessNextEvent ends up waiting for the new
1083 // events.
1084 bool mayWaitForWakeup = reallyWait && !mEvents->HasPendingEvent();
1085 #endif
1086
1087 nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserverOnThread();
1088 if (obs) {
1089 obs->OnProcessNextEvent(this, reallyWait);
1090 }
1091
1092 NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), OnProcessNextEvent,
1093 (this, reallyWait));
1094
1095 #ifdef MOZ_CANARY
1096 Canary canary;
1097 #endif
1098 nsresult rv = NS_OK;
1099
1100 {
1101 // Scope for |event| to make sure that its destructor fires while
1102 // mNestedEventLoopDepth has been incremented, since that destructor can
1103 // also do work.
1104 nsCOMPtr<nsIRunnable> event;
1105 bool usingTaskController = mIsMainThread;
1106 if (usingTaskController) {
1107 event = TaskController::Get()->GetRunnableForMTTask(reallyWait);
1108 } else {
1109 event = mEvents->GetEvent(reallyWait, &mLastEventDelay);
1110 }
1111
1112 *aResult = (event.get() != nullptr);
1113
1114 if (event) {
1115 #ifdef EARLY_BETA_OR_EARLIER
1116 if (mayWaitForWakeup && mThread) {
1117 ++mWakeupCount;
1118 if (mWakeupCount == kTelemetryWakeupCountLimit) {
1119 TimeStamp now = TimeStamp::Now();
1120 double ms = (now - mLastWakeupCheckTime).ToMilliseconds();
1121 if (ms < 0) {
1122 ms = 0;
1123 }
1124 const char* name = !mNameForWakeupTelemetry.IsEmpty()
1125 ? mNameForWakeupTelemetry.get()
1126 : PR_GetThreadName(mThread);
1127 if (!name) {
1128 name = mIsMainThread ? "MainThread" : "(nameless thread)";
1129 }
1130 nsDependentCString key(name);
1131 Telemetry::Accumulate(Telemetry::THREAD_WAKEUP, key,
1132 static_cast<uint32_t>(ms));
1133 mLastWakeupCheckTime = now;
1134 mWakeupCount = 0;
1135 }
1136 }
1137 #endif
1138
1139 LOG(("THRD(%p) running [%p]\n", this, event.get()));
1140
1141 Maybe<LogRunnable::Run> log;
1142
1143 if (!usingTaskController) {
1144 log.emplace(event);
1145 }
1146
1147 // Delay event processing to encourage whoever dispatched this event
1148 // to run.
1149 DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
1150
1151 mozilla::TimeStamp now = mozilla::TimeStamp::Now();
1152
1153 if (mUseHangMonitor) {
1154 BackgroundHangMonitor().NotifyActivity();
1155 }
1156
1157 Maybe<PerformanceCounterState::Snapshot> snapshot;
1158 if (!usingTaskController) {
1159 snapshot.emplace(mPerformanceCounterState.RunnableWillRun(
1160 GetPerformanceCounter(event), now, false));
1161 }
1162
1163 mLastEventStart = now;
1164
1165 if (!usingTaskController) {
1166 AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
1167 event->Run();
1168 } else {
1169 // Avoid generating "Runnable" profiler markers for the
1170 // "TaskController::ExecutePendingMTTasks" runnables created
1171 // by TaskController, which already adds "Runnable" markers
1172 // when executing tasks.
1173 event->Run();
1174 }
1175
1176 if (usingTaskController) {
1177 *aResult = TaskController::Get()->MTTaskRunnableProcessedTask();
1178 } else {
1179 mPerformanceCounterState.RunnableDidRun(std::move(snapshot.ref()));
1180 }
1181
1182 // To cover the event's destructor code inside the LogRunnable span.
1183 event = nullptr;
1184 } else {
1185 mLastEventDelay = TimeDuration();
1186 mLastEventStart = TimeStamp();
1187 if (aMayWait) {
1188 MOZ_ASSERT(ShuttingDown(),
1189 "This should only happen when shutting down");
1190 rv = NS_ERROR_UNEXPECTED;
1191 }
1192 }
1193 }
1194
1195 DrainDirectTasks();
1196
1197 NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), AfterProcessNextEvent,
1198 (this, *aResult));
1199
1200 if (obs) {
1201 obs->AfterProcessNextEvent(this, *aResult);
1202 }
1203
1204 // In case some EventObserver dispatched some direct tasks; process them
1205 // now.
1206 DrainDirectTasks();
1207
1208 if (callScriptObserver) {
1209 if (mScriptObserver) {
1210 mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
1211 }
1212 noJSAPI.reset();
1213 }
1214
1215 --mNestedEventLoopDepth;
1216
1217 return rv;
1218 }
1219
1220 //-----------------------------------------------------------------------------
1221 // nsISupportsPriority
1222
1223 NS_IMETHODIMP
GetPriority(int32_t * aPriority)1224 nsThread::GetPriority(int32_t* aPriority) {
1225 *aPriority = mPriority;
1226 return NS_OK;
1227 }
1228
1229 NS_IMETHODIMP
SetPriority(int32_t aPriority)1230 nsThread::SetPriority(int32_t aPriority) {
1231 if (NS_WARN_IF(!mThread)) {
1232 return NS_ERROR_NOT_INITIALIZED;
1233 }
1234
1235 // NSPR defines the following four thread priorities:
1236 // PR_PRIORITY_LOW
1237 // PR_PRIORITY_NORMAL
1238 // PR_PRIORITY_HIGH
1239 // PR_PRIORITY_URGENT
1240 // We map the priority values defined on nsISupportsPriority to these
1241 // values.
1242
1243 mPriority = aPriority;
1244
1245 PRThreadPriority pri;
1246 if (mPriority <= PRIORITY_HIGHEST) {
1247 pri = PR_PRIORITY_URGENT;
1248 } else if (mPriority < PRIORITY_NORMAL) {
1249 pri = PR_PRIORITY_HIGH;
1250 } else if (mPriority > PRIORITY_NORMAL) {
1251 pri = PR_PRIORITY_LOW;
1252 } else {
1253 pri = PR_PRIORITY_NORMAL;
1254 }
1255 // If chaos mode is active, retain the randomly chosen priority
1256 if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
1257 PR_SetThreadPriority(mThread, pri);
1258 }
1259
1260 return NS_OK;
1261 }
1262
1263 NS_IMETHODIMP
AdjustPriority(int32_t aDelta)1264 nsThread::AdjustPriority(int32_t aDelta) {
1265 return SetPriority(mPriority + aDelta);
1266 }
1267
1268 //-----------------------------------------------------------------------------
1269 // nsIThreadInternal
1270
1271 NS_IMETHODIMP
GetObserver(nsIThreadObserver ** aObs)1272 nsThread::GetObserver(nsIThreadObserver** aObs) {
1273 MOZ_ASSERT(mEvents);
1274 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1275
1276 nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
1277 obs.forget(aObs);
1278 return NS_OK;
1279 }
1280
1281 NS_IMETHODIMP
SetObserver(nsIThreadObserver * aObs)1282 nsThread::SetObserver(nsIThreadObserver* aObs) {
1283 MOZ_ASSERT(mEvents);
1284 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1285
1286 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1287 return NS_ERROR_NOT_SAME_THREAD;
1288 }
1289
1290 mEvents->SetObserver(aObs);
1291 return NS_OK;
1292 }
1293
RecursionDepth() const1294 uint32_t nsThread::RecursionDepth() const {
1295 MOZ_ASSERT(PR_GetCurrentThread() == mThread);
1296 return mNestedEventLoopDepth;
1297 }
1298
1299 NS_IMETHODIMP
AddObserver(nsIThreadObserver * aObserver)1300 nsThread::AddObserver(nsIThreadObserver* aObserver) {
1301 MOZ_ASSERT(mEvents);
1302 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1303
1304 if (NS_WARN_IF(!aObserver)) {
1305 return NS_ERROR_INVALID_ARG;
1306 }
1307 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1308 return NS_ERROR_NOT_SAME_THREAD;
1309 }
1310
1311 EventQueue()->AddObserver(aObserver);
1312
1313 return NS_OK;
1314 }
1315
1316 NS_IMETHODIMP
RemoveObserver(nsIThreadObserver * aObserver)1317 nsThread::RemoveObserver(nsIThreadObserver* aObserver) {
1318 MOZ_ASSERT(mEvents);
1319 NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
1320
1321 if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
1322 return NS_ERROR_NOT_SAME_THREAD;
1323 }
1324
1325 EventQueue()->RemoveObserver(aObserver);
1326
1327 return NS_OK;
1328 }
1329
SetScriptObserver(mozilla::CycleCollectedJSContext * aScriptObserver)1330 void nsThread::SetScriptObserver(
1331 mozilla::CycleCollectedJSContext* aScriptObserver) {
1332 if (!aScriptObserver) {
1333 mScriptObserver = nullptr;
1334 return;
1335 }
1336
1337 MOZ_ASSERT(!mScriptObserver);
1338 mScriptObserver = aScriptObserver;
1339 }
1340
1341 void NS_DispatchMemoryPressure();
1342
DoMainThreadSpecificProcessing() const1343 void nsThread::DoMainThreadSpecificProcessing() const {
1344 MOZ_ASSERT(mIsMainThread);
1345
1346 ipc::CancelCPOWs();
1347
1348 // Fire a memory pressure notification, if one is pending.
1349 if (!ShuttingDown()) {
1350 NS_DispatchMemoryPressure();
1351 }
1352 }
1353
1354 NS_IMETHODIMP
GetEventTarget(nsIEventTarget ** aEventTarget)1355 nsThread::GetEventTarget(nsIEventTarget** aEventTarget) {
1356 nsCOMPtr<nsIEventTarget> target = this;
1357 target.forget(aEventTarget);
1358 return NS_OK;
1359 }
1360
1361 //-----------------------------------------------------------------------------
1362 // nsIDirectTaskDispatcher
1363
1364 NS_IMETHODIMP
DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent)1365 nsThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
1366 if (!IsOnCurrentThread()) {
1367 return NS_ERROR_FAILURE;
1368 }
1369 mDirectTasks.AddTask(std::move(aEvent));
1370 return NS_OK;
1371 }
1372
DrainDirectTasks()1373 NS_IMETHODIMP nsThread::DrainDirectTasks() {
1374 if (!IsOnCurrentThread()) {
1375 return NS_ERROR_FAILURE;
1376 }
1377 mDirectTasks.DrainTasks();
1378 return NS_OK;
1379 }
1380
HaveDirectTasks(bool * aValue)1381 NS_IMETHODIMP nsThread::HaveDirectTasks(bool* aValue) {
1382 if (!IsOnCurrentThread()) {
1383 return NS_ERROR_FAILURE;
1384 }
1385
1386 *aValue = mDirectTasks.HaveTasks();
1387 return NS_OK;
1388 }
1389
EventTarget()1390 nsIEventTarget* nsThread::EventTarget() { return this; }
1391
SerialEventTarget()1392 nsISerialEventTarget* nsThread::SerialEventTarget() { return this; }
1393
OnDelayedRunnableCreated(mozilla::DelayedRunnable * aRunnable)1394 void nsThread::OnDelayedRunnableCreated(mozilla::DelayedRunnable* aRunnable) {
1395 mEventTarget->OnDelayedRunnableCreated(aRunnable);
1396 }
1397
OnDelayedRunnableScheduled(mozilla::DelayedRunnable * aRunnable)1398 void nsThread::OnDelayedRunnableScheduled(mozilla::DelayedRunnable* aRunnable) {
1399 mEventTarget->OnDelayedRunnableScheduled(aRunnable);
1400 }
1401
OnDelayedRunnableRan(mozilla::DelayedRunnable * aRunnable)1402 void nsThread::OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) {
1403 mEventTarget->OnDelayedRunnableRan(aRunnable);
1404 }
1405
EnterLocalExecution()1406 nsLocalExecutionRecord nsThread::EnterLocalExecution() {
1407 MOZ_RELEASE_ASSERT(!mIsInLocalExecutionMode);
1408 MOZ_ASSERT(IsOnCurrentThread());
1409 MOZ_ASSERT(EventQueue());
1410 return nsLocalExecutionRecord(*EventQueue(), mIsInLocalExecutionMode);
1411 }
1412
nsLocalExecutionGuard(nsLocalExecutionRecord && aLocalExecutionRecord)1413 nsLocalExecutionGuard::nsLocalExecutionGuard(
1414 nsLocalExecutionRecord&& aLocalExecutionRecord)
1415 : mEventQueueStack(aLocalExecutionRecord.mEventQueueStack),
1416 mLocalEventTarget(mEventQueueStack.PushEventQueue()),
1417 mLocalExecutionFlag(aLocalExecutionRecord.mLocalExecutionFlag) {
1418 MOZ_ASSERT(mLocalEventTarget);
1419 MOZ_ASSERT(!mLocalExecutionFlag);
1420 mLocalExecutionFlag = true;
1421 }
1422
~nsLocalExecutionGuard()1423 nsLocalExecutionGuard::~nsLocalExecutionGuard() {
1424 MOZ_ASSERT(mLocalExecutionFlag);
1425 mLocalExecutionFlag = false;
1426 mEventQueueStack.PopEventQueue(mLocalEventTarget);
1427 }
1428
NS_IMPL_ISUPPORTS(nsThreadShutdownContext,nsIThreadShutdown)1429 NS_IMPL_ISUPPORTS(nsThreadShutdownContext, nsIThreadShutdown)
1430
1431 NS_IMETHODIMP
1432 nsThreadShutdownContext::OnCompletion(nsIRunnable* aEvent) {
1433 if (mCompleted) {
1434 aEvent->Run();
1435 } else {
1436 mCompletionCallbacks.AppendElement(aEvent);
1437 }
1438 return NS_OK;
1439 }
1440
1441 NS_IMETHODIMP
GetCompleted(bool * aCompleted)1442 nsThreadShutdownContext::GetCompleted(bool* aCompleted) {
1443 *aCompleted = mCompleted;
1444 return NS_OK;
1445 }
1446
1447 NS_IMETHODIMP
StopWaitingAndLeakThread()1448 nsThreadShutdownContext::StopWaitingAndLeakThread() {
1449 // Take the joining thread from `mJoiningThread` so that the terminating
1450 // thread won't try to dispatch nsThreadShutdownAckEvent to us anymore.
1451 RefPtr<nsThread> joiningThread;
1452 {
1453 auto lock = mJoiningThread.Lock();
1454 joiningThread = lock->forget();
1455 }
1456 if (!joiningThread) {
1457 // Shutdown is already being resolved, so there's nothing for us to do.
1458 return NS_ERROR_NOT_AVAILABLE;
1459 }
1460
1461 MOZ_DIAGNOSTIC_ASSERT(joiningThread->IsOnCurrentThread());
1462 MarkCompleted();
1463 return NS_OK;
1464 }
1465
MarkCompleted()1466 void nsThreadShutdownContext::MarkCompleted() {
1467 MOZ_ASSERT(!mCompleted);
1468 mCompleted = true;
1469 nsTArray<nsCOMPtr<nsIRunnable>> callbacks(std::move(mCompletionCallbacks));
1470 for (auto& callback : callbacks) {
1471 callback->Run();
1472 }
1473 }
1474
1475 namespace mozilla {
RunnableWillRun(PerformanceCounter * aCounter,TimeStamp aNow,bool aIsIdleRunnable)1476 PerformanceCounterState::Snapshot PerformanceCounterState::RunnableWillRun(
1477 PerformanceCounter* aCounter, TimeStamp aNow, bool aIsIdleRunnable) {
1478 if (IsNestedRunnable()) {
1479 // Flush out any accumulated time that should be accounted to the
1480 // current runnable before we start running a nested runnable.
1481 MaybeReportAccumulatedTime(aNow);
1482 }
1483
1484 Snapshot snapshot(mCurrentEventLoopDepth, mCurrentPerformanceCounter,
1485 mCurrentRunnableIsIdleRunnable);
1486
1487 mCurrentEventLoopDepth = mNestedEventLoopDepth;
1488 mCurrentPerformanceCounter = aCounter;
1489 mCurrentRunnableIsIdleRunnable = aIsIdleRunnable;
1490 mCurrentTimeSliceStart = aNow;
1491
1492 return snapshot;
1493 }
1494
RunnableDidRun(Snapshot && aSnapshot)1495 void PerformanceCounterState::RunnableDidRun(Snapshot&& aSnapshot) {
1496 // First thing: Restore our mCurrentEventLoopDepth so we can use
1497 // IsNestedRunnable().
1498 mCurrentEventLoopDepth = aSnapshot.mOldEventLoopDepth;
1499
1500 // We may not need the current timestamp; don't bother computing it if we
1501 // don't.
1502 TimeStamp now;
1503 if (mCurrentPerformanceCounter || mIsMainThread || IsNestedRunnable()) {
1504 now = TimeStamp::Now();
1505 }
1506 if (mCurrentPerformanceCounter || mIsMainThread) {
1507 MaybeReportAccumulatedTime(now);
1508 }
1509
1510 // And now restore the rest of our state.
1511 mCurrentPerformanceCounter = std::move(aSnapshot.mOldPerformanceCounter);
1512 mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable;
1513 if (IsNestedRunnable()) {
1514 // Reset mCurrentTimeSliceStart to right now, so our parent runnable's
1515 // next slice can be properly accounted for.
1516 mCurrentTimeSliceStart = now;
1517 } else {
1518 // We are done at the outermost level; we are no longer in a timeslice.
1519 mCurrentTimeSliceStart = TimeStamp();
1520 }
1521 }
1522
MaybeReportAccumulatedTime(TimeStamp aNow)1523 void PerformanceCounterState::MaybeReportAccumulatedTime(TimeStamp aNow) {
1524 MOZ_ASSERT(mCurrentTimeSliceStart,
1525 "How did we get here if we're not in a timeslice?");
1526
1527 if (!mCurrentPerformanceCounter && !mIsMainThread) {
1528 // No one cares about this timeslice.
1529 return;
1530 }
1531
1532 TimeDuration duration = aNow - mCurrentTimeSliceStart;
1533 if (mCurrentPerformanceCounter) {
1534 mCurrentPerformanceCounter->IncrementExecutionDuration(
1535 duration.ToMicroseconds());
1536 }
1537
1538 // Long tasks only matter on the main thread.
1539 if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_BUSY_WINDOW_MS) {
1540 // Idle events (gc...) don't *really* count here
1541 if (!mCurrentRunnableIsIdleRunnable) {
1542 mLastLongNonIdleTaskEnd = aNow;
1543 }
1544 mLastLongTaskEnd = aNow;
1545
1546 if (profiler_thread_is_being_profiled_for_markers()) {
1547 struct LongTaskMarker {
1548 static constexpr Span<const char> MarkerTypeName() {
1549 return MakeStringSpan("MainThreadLongTask");
1550 }
1551 static void StreamJSONMarkerData(
1552 baseprofiler::SpliceableJSONWriter& aWriter) {
1553 aWriter.StringProperty("category", "LongTask");
1554 }
1555 static MarkerSchema MarkerTypeDisplay() {
1556 using MS = MarkerSchema;
1557 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
1558 schema.AddKeyLabelFormat("category", "Type", MS::Format::String);
1559 return schema;
1560 }
1561 };
1562
1563 profiler_add_marker(mCurrentRunnableIsIdleRunnable
1564 ? ProfilerString8View("LongIdleTask")
1565 : ProfilerString8View("LongTask"),
1566 geckoprofiler::category::OTHER,
1567 MarkerTiming::Interval(mCurrentTimeSliceStart, aNow),
1568 LongTaskMarker{});
1569 }
1570 }
1571 }
1572
1573 } // namespace mozilla
1574