forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1560 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1560 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsThread.h"
 | |
| 
 | |
| #include "base/message_loop.h"
 | |
| #include "base/platform_thread.h"
 | |
| 
 | |
| // Chromium's logging can sometimes leak through...
 | |
| #ifdef LOG
 | |
| #  undef LOG
 | |
| #endif
 | |
| 
 | |
| #include "mozilla/ReentrantMonitor.h"
 | |
| #include "nsMemoryPressure.h"
 | |
| #include "nsThreadManager.h"
 | |
| #include "nsIClassInfoImpl.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsQueryObject.h"
 | |
| #include "pratom.h"
 | |
| #include "mozilla/BackgroundHangMonitor.h"
 | |
| #include "mozilla/CycleCollectedJSContext.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "mozilla/IOInterposer.h"
 | |
| #include "mozilla/ipc/MessageChannel.h"
 | |
| #include "mozilla/ipc/BackgroundChild.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/ProfilerRunnable.h"
 | |
| #include "mozilla/SchedulerGroup.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "mozilla/SpinEventLoopUntil.h"
 | |
| #include "mozilla/StaticLocalPtr.h"
 | |
| #include "mozilla/StaticPrefs_threads.h"
 | |
| #include "mozilla/TaskController.h"
 | |
| #include "nsXPCOMPrivate.h"
 | |
| #include "mozilla/ChaosMode.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "mozilla/Unused.h"
 | |
| #include "mozilla/dom/DocGroup.h"
 | |
| #include "mozilla/dom/ScriptSettings.h"
 | |
| #include "nsThreadSyncDispatch.h"
 | |
| #include "nsServiceManagerUtils.h"
 | |
| #include "GeckoProfiler.h"
 | |
| #include "ThreadEventQueue.h"
 | |
| #include "ThreadEventTarget.h"
 | |
| #include "ThreadDelay.h"
 | |
| 
 | |
| #include <limits>
 | |
| 
 | |
| #ifdef XP_LINUX
 | |
| #  ifdef __GLIBC__
 | |
| #    include <gnu/libc-version.h>
 | |
| #  endif
 | |
| #  include <sys/mman.h>
 | |
| #  include <sys/time.h>
 | |
| #  include <sys/resource.h>
 | |
| #  include <sched.h>
 | |
| #  include <stdio.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| #  include "mozilla/DynamicallyLinkedFunctionPtr.h"
 | |
| 
 | |
| #  include <winbase.h>
 | |
| 
 | |
| using GetCurrentThreadStackLimitsFn = void(WINAPI*)(PULONG_PTR LowLimit,
 | |
|                                                     PULONG_PTR HighLimit);
 | |
| #endif
 | |
| 
 | |
| #define HAVE_UALARM                                                        \
 | |
|   _BSD_SOURCE ||                                                           \
 | |
|       (_XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
 | |
|           !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
 | |
| 
 | |
| #if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE)
 | |
| #  define HAVE_SCHED_SETAFFINITY
 | |
| #endif
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
| #  include <mach/mach.h>
 | |
| #  include <mach/thread_policy.h>
 | |
| #  include <sys/qos.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef MOZ_CANARY
 | |
| #  include <unistd.h>
 | |
| #  include <execinfo.h>
 | |
| #  include <signal.h>
 | |
| #  include <fcntl.h>
 | |
| #  include "nsXULAppAPI.h"
 | |
| #endif
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| extern void InitThreadLocalVariables();
 | |
| 
 | |
| static LazyLogModule sThreadLog("nsThread");
 | |
| #ifdef LOG
 | |
| #  undef LOG
 | |
| #endif
 | |
| #define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args)
 | |
| 
 | |
| NS_DECL_CI_INTERFACE_GETTER(nsThread)
 | |
| 
 | |
| Array<char, nsThread::kRunnableNameBufSize> nsThread::sMainThreadRunnableName;
 | |
| 
 | |
| #ifdef EARLY_BETA_OR_EARLIER
 | |
| const uint32_t kTelemetryWakeupCountLimit = 100;
 | |
| #endif
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
 | |
| // somewhat manually.
 | |
| 
 | |
| class nsThreadClassInfo : public nsIClassInfo {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS_INHERITED  // no mRefCnt
 | |
|       NS_DECL_NSICLASSINFO
 | |
| 
 | |
|       nsThreadClassInfo() = default;
 | |
| };
 | |
| 
 | |
| NS_IMETHODIMP_(MozExternalRefCountType)
 | |
| nsThreadClassInfo::AddRef() { return 2; }
 | |
| NS_IMETHODIMP_(MozExternalRefCountType)
 | |
| nsThreadClassInfo::Release() { return 1; }
 | |
| NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) {
 | |
|   return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aArray);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) {
 | |
|   *aResult = nullptr;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadClassInfo::GetContractID(nsACString& aResult) {
 | |
|   aResult.SetIsVoid(true);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadClassInfo::GetClassDescription(nsACString& aResult) {
 | |
|   aResult.SetIsVoid(true);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadClassInfo::GetClassID(nsCID** aResult) {
 | |
|   *aResult = nullptr;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadClassInfo::GetFlags(uint32_t* aResult) {
 | |
|   *aResult = THREADSAFE;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) {
 | |
|   return NS_ERROR_NOT_AVAILABLE;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMPL_ADDREF(nsThread)
 | |
| NS_IMPL_RELEASE(nsThread)
 | |
| NS_INTERFACE_MAP_BEGIN(nsThread)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIThread)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIThreadInternal)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher)
 | |
|   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
 | |
|   if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
 | |
|     static nsThreadClassInfo sThreadClassInfo;
 | |
|     foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo);
 | |
|   } else
 | |
| NS_INTERFACE_MAP_END
 | |
| NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal,
 | |
|                             nsIEventTarget, nsISerialEventTarget,
 | |
|                             nsISupportsPriority)
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| // This event is responsible for notifying nsThread::Shutdown that it is time
 | |
| // to call PR_JoinThread. It implements nsICancelableRunnable so that it can
 | |
| // run on a DOM Worker thread (where all events must implement
 | |
| // nsICancelableRunnable.)
 | |
| class nsThreadShutdownAckEvent : public CancelableRunnable {
 | |
|  public:
 | |
|   explicit nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext*> aCtx)
 | |
|       : CancelableRunnable("nsThreadShutdownAckEvent"),
 | |
|         mShutdownContext(aCtx) {}
 | |
|   NS_IMETHOD Run() override {
 | |
|     mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext);
 | |
|     return NS_OK;
 | |
|   }
 | |
|   nsresult Cancel() override { return Run(); }
 | |
| 
 | |
|  private:
 | |
|   virtual ~nsThreadShutdownAckEvent() = default;
 | |
| 
 | |
|   NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext;
 | |
| };
 | |
| 
 | |
| // This event is responsible for setting mShutdownContext
 | |
| class nsThreadShutdownEvent : public Runnable {
 | |
|  public:
 | |
|   nsThreadShutdownEvent(NotNull<nsThread*> aThr,
 | |
|                         NotNull<nsThreadShutdownContext*> aCtx)
 | |
|       : Runnable("nsThreadShutdownEvent"),
 | |
|         mThread(aThr),
 | |
|         mShutdownContext(aCtx) {}
 | |
|   NS_IMETHOD Run() override {
 | |
|     // Creates a cycle between `mThread` and the shutdown context which will be
 | |
|     // broken when the thread exits.
 | |
|     mThread->mShutdownContext = mShutdownContext;
 | |
|     MessageLoop::current()->Quit();
 | |
| #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     // Let's leave a trace that we passed here in the thread's name.
 | |
|     nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread()));
 | |
|     threadName.Append(",SHDRCV"_ns);
 | |
|     NS_SetCurrentThreadName(threadName.get());
 | |
| #endif
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   NotNull<RefPtr<nsThread>> mThread;
 | |
|   NotNull<RefPtr<nsThreadShutdownContext>> mShutdownContext;
 | |
| };
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| static void SetThreadAffinity(unsigned int cpu) {
 | |
| #ifdef HAVE_SCHED_SETAFFINITY
 | |
|   cpu_set_t cpus;
 | |
|   CPU_ZERO(&cpus);
 | |
|   CPU_SET(cpu, &cpus);
 | |
|   sched_setaffinity(0, sizeof(cpus), &cpus);
 | |
|   // Don't assert sched_setaffinity's return value because it intermittently (?)
 | |
|   // fails with EINVAL on Linux x64 try runs.
 | |
| #elif defined(XP_MACOSX)
 | |
|   // OS X does not provide APIs to pin threads to specific processors, but you
 | |
|   // can tag threads as belonging to the same "affinity set" and the OS will try
 | |
|   // to run them on the same processor. To run threads on different processors,
 | |
|   // tag them as belonging to different affinity sets. Tag 0, the default, means
 | |
|   // "no affinity" so let's pretend each CPU has its own tag `cpu+1`.
 | |
|   thread_affinity_policy_data_t policy;
 | |
|   policy.affinity_tag = cpu + 1;
 | |
|   kern_return_t kr = thread_policy_set(
 | |
|       mach_thread_self(), THREAD_AFFINITY_POLICY, &policy.affinity_tag, 1);
 | |
|   // Setting the thread affinity is not supported on ARM.
 | |
|   MOZ_ALWAYS_TRUE(kr == KERN_SUCCESS || kr == KERN_NOT_SUPPORTED);
 | |
| #elif defined(XP_WIN)
 | |
|   MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) !=
 | |
|                   (DWORD)-1);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static void SetupCurrentThreadForChaosMode() {
 | |
|   if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef XP_LINUX
 | |
|   // PR_SetThreadPriority doesn't really work since priorities >
 | |
|   // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use
 | |
|   // setpriority(2) to set random 'nice values'. In regular Linux this is only
 | |
|   // a dynamic adjustment so it still doesn't really do what we want, but tools
 | |
|   // like 'rr' can be more aggressive about honoring these values.
 | |
|   // Some of these calls may fail due to trying to lower the priority
 | |
|   // (e.g. something may have already called setpriority() for this thread).
 | |
|   // This makes it hard to have non-main threads with higher priority than the
 | |
|   // main thread, but that's hard to fix. Tools like rr can choose to honor the
 | |
|   // requested values anyway.
 | |
|   // Use just 4 priorities so there's a reasonable chance of any two threads
 | |
|   // having equal priority.
 | |
|   setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4));
 | |
| #else
 | |
|   // We should set the affinity here but NSPR doesn't provide a way to expose
 | |
|   // it.
 | |
|   uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1);
 | |
|   PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority));
 | |
| #endif
 | |
| 
 | |
|   // Force half the threads to CPU 0 so they compete for CPU
 | |
|   if (ChaosMode::randomUint32LessThan(2)) {
 | |
|     SetThreadAffinity(0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| struct ThreadInitData {
 | |
|   RefPtr<nsThread> thread;
 | |
|   nsCString name;
 | |
| };
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| void nsThread::MaybeRemoveFromThreadList() {
 | |
|   nsThreadManager& tm = nsThreadManager::get();
 | |
|   OffTheBooksMutexAutoLock mal(tm.ThreadListMutex());
 | |
|   if (isInList()) {
 | |
|     removeFrom(tm.ThreadList());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*static*/
 | |
| void nsThread::ThreadFunc(void* aArg) {
 | |
|   using mozilla::ipc::BackgroundChild;
 | |
| 
 | |
|   UniquePtr<ThreadInitData> initData(static_cast<ThreadInitData*>(aArg));
 | |
|   RefPtr<nsThread>& self = initData->thread;
 | |
| 
 | |
|   MOZ_ASSERT(self->mEventTarget);
 | |
|   MOZ_ASSERT(self->mEvents);
 | |
| 
 | |
|   // Note: see the comment in nsThread::Init, where we set these same values.
 | |
|   DebugOnly<PRThread*> prev = self->mThread.exchange(PR_GetCurrentThread());
 | |
|   MOZ_ASSERT(!prev || prev == PR_GetCurrentThread());
 | |
|   self->mEventTarget->SetCurrentThread(self->mThread);
 | |
|   SetupCurrentThreadForChaosMode();
 | |
| 
 | |
|   if (!initData->name.IsEmpty()) {
 | |
|     NS_SetCurrentThreadName(initData->name.BeginReading());
 | |
|   }
 | |
| 
 | |
|   self->InitCommon();
 | |
| 
 | |
|   // Inform the ThreadManager
 | |
|   nsThreadManager::get().RegisterCurrentThread(*self);
 | |
| 
 | |
|   mozilla::IOInterposer::RegisterCurrentThread();
 | |
| 
 | |
|   // This must come after the call to nsThreadManager::RegisterCurrentThread(),
 | |
|   // because that call is needed to properly set up this thread as an nsThread,
 | |
|   // which profiler_register_thread() requires. See bug 1347007.
 | |
|   const bool registerWithProfiler = !initData->name.IsEmpty();
 | |
|   if (registerWithProfiler) {
 | |
|     PROFILER_REGISTER_THREAD(initData->name.BeginReading());
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     // Scope for MessageLoop.
 | |
|     MessageLoop loop(
 | |
| #if defined(XP_WIN) || defined(XP_MACOSX)
 | |
|         self->mIsUiThread ? MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD
 | |
|                           : MessageLoop::TYPE_MOZILLA_NONMAINTHREAD,
 | |
| #else
 | |
|         MessageLoop::TYPE_MOZILLA_NONMAINTHREAD,
 | |
| #endif
 | |
|         self);
 | |
| 
 | |
|     // Now, process incoming events...
 | |
|     loop.Run();
 | |
| 
 | |
|     self->mEvents->RunShutdownTasks();
 | |
| 
 | |
|     BackgroundChild::CloseForCurrentThread();
 | |
| 
 | |
|     // NB: The main thread does not shut down here!  It shuts down via
 | |
|     // nsThreadManager::Shutdown.
 | |
| 
 | |
|     // Do NS_ProcessPendingEvents but with special handling to set
 | |
|     // mEventsAreDoomed atomically with the removal of the last event. The key
 | |
|     // invariant here is that we will never permit PutEvent to succeed if the
 | |
|     // event would be left in the queue after our final call to
 | |
|     // NS_ProcessPendingEvents. We also have to keep processing events as long
 | |
|     // as we have outstanding mRequestedShutdownContexts.
 | |
|     while (true) {
 | |
|       // Check and see if we're waiting on any threads.
 | |
|       self->WaitForAllAsynchronousShutdowns();
 | |
| 
 | |
|       if (self->mEvents->ShutdownIfNoPendingEvents()) {
 | |
|         break;
 | |
|       }
 | |
|       NS_ProcessPendingEvents(self);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mozilla::IOInterposer::UnregisterCurrentThread();
 | |
| 
 | |
|   // Inform the threadmanager that this thread is going away
 | |
|   nsThreadManager::get().UnregisterCurrentThread(*self);
 | |
| 
 | |
|   // The thread should only unregister itself if it was registered above.
 | |
|   if (registerWithProfiler) {
 | |
|     PROFILER_UNREGISTER_THREAD();
 | |
|   }
 | |
| 
 | |
|   NotNull<RefPtr<nsThreadShutdownContext>> context =
 | |
|       WrapNotNull(self->mShutdownContext);
 | |
|   self->mShutdownContext = nullptr;
 | |
|   MOZ_ASSERT(context->mTerminatingThread == self);
 | |
| 
 | |
|   // Take the joining thread from our shutdown context. This may have been
 | |
|   // cleared by the joining thread if it decided to cancel waiting on us, in
 | |
|   // which case we won't notify our caller, and leak.
 | |
|   RefPtr<nsThread> joiningThread;
 | |
|   {
 | |
|     MutexAutoLock lock(context->mJoiningThreadMutex);
 | |
|     joiningThread = context->mJoiningThread.forget();
 | |
|     MOZ_RELEASE_ASSERT(joiningThread || context->mThreadLeaked);
 | |
|   }
 | |
|   if (joiningThread) {
 | |
|     // Dispatch shutdown ACK
 | |
|     nsCOMPtr<nsIRunnable> event = new nsThreadShutdownAckEvent(context);
 | |
|     nsresult dispatch_ack_rv =
 | |
|         joiningThread->Dispatch(event, NS_DISPATCH_NORMAL);
 | |
| 
 | |
|     // We do not expect this to ever happen, but If we cannot dispatch
 | |
|     // the ack event, someone probably blocks waiting on us and will
 | |
|     // crash with a hang later anyways. The best we can do is to tell
 | |
|     // the world what happened right here.
 | |
|     MOZ_RELEASE_ASSERT(NS_SUCCEEDED(dispatch_ack_rv));
 | |
| 
 | |
| #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     // Let's leave a trace that we passed here in the thread's name.
 | |
|     nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread()));
 | |
|     threadName.Append(",SHDACK"_ns);
 | |
|     NS_SetCurrentThreadName(threadName.get());
 | |
| #endif
 | |
|   } else {
 | |
|     NS_WARNING(
 | |
|         "nsThread exiting after StopWaitingAndLeakThread was called, thread "
 | |
|         "resources will be leaked!");
 | |
|   }
 | |
| 
 | |
|   // Release any observer of the thread here.
 | |
|   self->SetObserver(nullptr);
 | |
| 
 | |
|   // The PRThread will be deleted in PR_JoinThread(), so clear references.
 | |
|   self->mThread = nullptr;
 | |
|   self->mEventTarget->ClearCurrentThread();
 | |
| }
 | |
| 
 | |
| void nsThread::InitCommon() {
 | |
|   mThreadId = uint32_t(PlatformThread::CurrentId());
 | |
| 
 | |
|   {
 | |
| #if defined(XP_LINUX)
 | |
|     pthread_attr_t attr;
 | |
|     pthread_attr_init(&attr);
 | |
|     pthread_getattr_np(pthread_self(), &attr);
 | |
| 
 | |
|     size_t stackSize;
 | |
|     pthread_attr_getstack(&attr, &mStackBase, &stackSize);
 | |
| 
 | |
|     // Glibc prior to 2.27 reports the stack size and base including the guard
 | |
|     // region, so we need to compensate for it to get accurate accounting.
 | |
|     // Also, this behavior difference isn't guarded by a versioned symbol, so we
 | |
|     // actually need to check the runtime glibc version, not the version we were
 | |
|     // compiled against.
 | |
|     static bool sAdjustForGuardSize = ({
 | |
| #  ifdef __GLIBC__
 | |
|       unsigned major, minor;
 | |
|       sscanf(gnu_get_libc_version(), "%u.%u", &major, &minor) < 2 ||
 | |
|           major < 2 || (major == 2 && minor < 27);
 | |
| #  else
 | |
|       false;
 | |
| #  endif
 | |
|     });
 | |
|     if (sAdjustForGuardSize) {
 | |
|       size_t guardSize;
 | |
|       pthread_attr_getguardsize(&attr, &guardSize);
 | |
| 
 | |
|       // Note: This assumes that the stack grows down, as is the case on all of
 | |
|       // our tier 1 platforms. On platforms where the stack grows up, the
 | |
|       // mStackBase adjustment is unnecessary, but doesn't cause any harm other
 | |
|       // than under-counting stack memory usage by one page.
 | |
|       mStackBase = reinterpret_cast<char*>(mStackBase) + guardSize;
 | |
|       stackSize -= guardSize;
 | |
|     }
 | |
| 
 | |
|     mStackSize = stackSize;
 | |
| 
 | |
|     // This is a bit of a hack.
 | |
|     //
 | |
|     // We really do want the NOHUGEPAGE flag on our thread stacks, since we
 | |
|     // don't expect any of them to need anywhere near 2MB of space. But setting
 | |
|     // it here is too late to have an effect, since the first stack page has
 | |
|     // already been faulted in existence, and NSPR doesn't give us a way to set
 | |
|     // it beforehand.
 | |
|     //
 | |
|     // What this does get us, however, is a different set of VM flags on our
 | |
|     // thread stacks compared to normal heap memory. Which makes the Linux
 | |
|     // kernel report them as separate regions, even when they are adjacent to
 | |
|     // heap memory. This allows us to accurately track the actual memory
 | |
|     // consumption of our allocated stacks.
 | |
|     madvise(mStackBase, stackSize, MADV_NOHUGEPAGE);
 | |
| 
 | |
|     pthread_attr_destroy(&attr);
 | |
| #elif defined(XP_WIN)
 | |
|     static const StaticDynamicallyLinkedFunctionPtr<
 | |
|         GetCurrentThreadStackLimitsFn>
 | |
|         sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits");
 | |
| 
 | |
|     if (sGetStackLimits) {
 | |
|       ULONG_PTR stackBottom, stackTop;
 | |
|       sGetStackLimits(&stackBottom, &stackTop);
 | |
|       mStackBase = reinterpret_cast<void*>(stackBottom);
 | |
|       mStackSize = stackTop - stackBottom;
 | |
|     }
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   InitThreadLocalVariables();
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| #ifdef MOZ_CANARY
 | |
| int sCanaryOutputFD = -1;
 | |
| #endif
 | |
| 
 | |
| nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue,
 | |
|                    MainThreadFlag aMainThread,
 | |
|                    nsIThreadManager::ThreadCreationOptions aOptions)
 | |
|     : mEvents(aQueue.get()),
 | |
|       mEventTarget(new ThreadEventTarget(
 | |
|           mEvents.get(), aMainThread == MAIN_THREAD, aOptions.blockDispatch)),
 | |
|       mOutstandingShutdownContexts(0),
 | |
|       mShutdownContext(nullptr),
 | |
|       mScriptObserver(nullptr),
 | |
|       mThreadName("<uninitialized>"),
 | |
|       mStackSize(aOptions.stackSize),
 | |
|       mNestedEventLoopDepth(0),
 | |
|       mShutdownRequired(false),
 | |
|       mPriority(PRIORITY_NORMAL),
 | |
|       mIsMainThread(aMainThread == MAIN_THREAD),
 | |
|       mUseHangMonitor(aMainThread == MAIN_THREAD),
 | |
|       mIsUiThread(aOptions.isUiThread),
 | |
|       mIsAPoolThreadFree(nullptr),
 | |
|       mCanInvokeJS(false),
 | |
| #ifdef EARLY_BETA_OR_EARLIER
 | |
|       mLastWakeupCheckTime(TimeStamp::Now()),
 | |
| #endif
 | |
|       mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread,
 | |
|                                aOptions.longTaskLength) {
 | |
| #if !(defined(XP_WIN) || defined(XP_MACOSX))
 | |
|   MOZ_ASSERT(!mIsUiThread,
 | |
|              "Non-main UI threads are only supported on Windows and macOS");
 | |
| #endif
 | |
|   if (mIsMainThread) {
 | |
|     MOZ_ASSERT(!mIsUiThread,
 | |
|                "Setting isUIThread is not supported for main threads");
 | |
|     mozilla::TaskController::Get()->SetPerformanceCounterState(
 | |
|         &mPerformanceCounterState);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsThread::nsThread()
 | |
|     : mEvents(nullptr),
 | |
|       mEventTarget(nullptr),
 | |
|       mOutstandingShutdownContexts(0),
 | |
|       mShutdownContext(nullptr),
 | |
|       mScriptObserver(nullptr),
 | |
|       mThreadName("<uninitialized>"),
 | |
|       mStackSize(0),
 | |
|       mNestedEventLoopDepth(0),
 | |
|       mShutdownRequired(false),
 | |
|       mPriority(PRIORITY_NORMAL),
 | |
|       mIsMainThread(false),
 | |
|       mUseHangMonitor(false),
 | |
|       mIsUiThread(false),
 | |
|       mCanInvokeJS(false),
 | |
| #ifdef EARLY_BETA_OR_EARLIER
 | |
|       mLastWakeupCheckTime(TimeStamp::Now()),
 | |
| #endif
 | |
|       mPerformanceCounterState(mNestedEventLoopDepth) {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| }
 | |
| 
 | |
| nsThread::~nsThread() {
 | |
|   NS_ASSERTION(mOutstandingShutdownContexts == 0,
 | |
|                "shouldn't be waiting on other threads to shutdown");
 | |
| 
 | |
|   MaybeRemoveFromThreadList();
 | |
| }
 | |
| 
 | |
| nsresult nsThread::Init(const nsACString& aName) {
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   MOZ_ASSERT(!mThread);
 | |
| 
 | |
|   SetThreadNameInternal(aName);
 | |
| 
 | |
|   PRThread* thread = nullptr;
 | |
| 
 | |
|   nsThreadManager& tm = nsThreadManager::get();
 | |
|   {
 | |
|     OffTheBooksMutexAutoLock lock(tm.ThreadListMutex());
 | |
|     if (!tm.AllowNewXPCOMThreadsLocked()) {
 | |
|       return NS_ERROR_NOT_INITIALIZED;
 | |
|     }
 | |
| 
 | |
|     // We need to fully start the thread while holding the thread list lock, as
 | |
|     // the next acquire of the lock could try to shut down this thread (e.g.
 | |
|     // during xpcom shutdown), which would hang if `PR_CreateThread` failed.
 | |
| 
 | |
|     UniquePtr<ThreadInitData> initData(
 | |
|         new ThreadInitData{this, nsCString(aName)});
 | |
| 
 | |
|     // ThreadFunc is responsible for setting mThread
 | |
|     if (!(thread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, initData.get(),
 | |
|                                    PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
 | |
|                                    PR_JOINABLE_THREAD, mStackSize))) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
| 
 | |
|     // The created thread now owns initData, so release our ownership of it.
 | |
|     Unused << initData.release();
 | |
| 
 | |
|     // The thread has successfully started, so we can mark it as requiring
 | |
|     // shutdown & add it to the thread list.
 | |
|     mShutdownRequired = true;
 | |
|     tm.ThreadList().insertBack(this);
 | |
|   }
 | |
| 
 | |
|   // Note: we set these both here and inside ThreadFunc, to what should be
 | |
|   // the same value. This is because calls within ThreadFunc need these values
 | |
|   // to be set, and our callers need these values to be set.
 | |
|   DebugOnly<PRThread*> prev = mThread.exchange(thread);
 | |
|   MOZ_ASSERT(!prev || prev == thread);
 | |
| 
 | |
|   mEventTarget->SetCurrentThread(thread);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsThread::InitCurrentThread() {
 | |
|   mThread = PR_GetCurrentThread();
 | |
| 
 | |
|   nsThreadManager& tm = nsThreadManager::get();
 | |
|   {
 | |
|     OffTheBooksMutexAutoLock lock(tm.ThreadListMutex());
 | |
|     // NOTE: We don't check AllowNewXPCOMThreads here, as threads initialized
 | |
|     // this way do not need shutdown, so are OK to create after nsThreadManager
 | |
|     // shutdown. In addition, the main thread is initialized this way, which
 | |
|     // happens before AllowNewXPCOMThreads begins to return true.
 | |
|     tm.ThreadList().insertBack(this);
 | |
|   }
 | |
| 
 | |
|   SetupCurrentThreadForChaosMode();
 | |
|   InitCommon();
 | |
| 
 | |
|   tm.RegisterCurrentThread(*this);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsThread::GetThreadName(nsACString& aNameBuffer) {
 | |
|   auto lock = mThreadName.Lock();
 | |
|   aNameBuffer = lock.ref();
 | |
| }
 | |
| 
 | |
| void nsThread::SetThreadNameInternal(const nsACString& aName) {
 | |
|   auto lock = mThreadName.Lock();
 | |
|   lock->Assign(aName);
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsIEventTarget
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> event(aEvent);
 | |
|   return mEventTarget->Dispatch(event.forget(), aFlags);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */ nullptr, aFlags));
 | |
| 
 | |
|   return mEventTarget->Dispatch(std::move(aEvent), aFlags);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
 | |
|                           uint32_t aDelayMs) {
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   return mEventTarget->RegisterShutdownTask(aTask);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   return mEventTarget->UnregisterShutdownTask(aTask);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
 | |
|   if (mIsAPoolThreadFree && *mIsAPoolThreadFree) {
 | |
|     // if there are unstarted threads in the pool, a new event to the
 | |
|     // pool would not be delayed at all (beyond thread start time)
 | |
|     *aDelay = TimeDuration();
 | |
|     *aStart = TimeStamp();
 | |
|   } else {
 | |
|     *aDelay = mLastEventDelay;
 | |
|     *aStart = mLastEventStart;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
 | |
|   mLastEventDelay = aDelay;
 | |
|   mLastEventStart = aStart;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::IsOnCurrentThread(bool* aResult) {
 | |
|   if (mEventTarget) {
 | |
|     return mEventTarget->IsOnCurrentThread(aResult);
 | |
|   }
 | |
|   *aResult = PR_GetCurrentThread() == mThread;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP_(bool)
 | |
| nsThread::IsOnCurrentThreadInfallible() {
 | |
|   // This method is only going to be called if `mThread` is null, which
 | |
|   // only happens when the thread has exited the event loop.  Therefore, when
 | |
|   // we are called, we can never be on this thread.
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsIThread
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::GetPRThread(PRThread** aResult) {
 | |
|   PRThread* thread = mThread;  // atomic load
 | |
|   *aResult = thread;
 | |
|   return thread ? NS_OK : NS_ERROR_NOT_AVAILABLE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::GetCanInvokeJS(bool* aResult) {
 | |
|   *aResult = mCanInvokeJS;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::SetCanInvokeJS(bool aCanInvokeJS) {
 | |
|   mCanInvokeJS = aCanInvokeJS;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::GetLastLongTaskEnd(TimeStamp* _retval) {
 | |
|   *_retval = mPerformanceCounterState.LastLongTaskEnd();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) {
 | |
|   *_retval = mPerformanceCounterState.LastLongNonIdleTaskEnd();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::SetNameForWakeupTelemetry(const nsACString& aName) {
 | |
| #ifdef EARLY_BETA_OR_EARLIER
 | |
|   mNameForWakeupTelemetry = aName;
 | |
| #endif
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::AsyncShutdown() {
 | |
|   LOG(("THRD(%p) async shutdown\n", this));
 | |
| 
 | |
|   nsCOMPtr<nsIThreadShutdown> shutdown;
 | |
|   BeginShutdown(getter_AddRefs(shutdown));
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::BeginShutdown(nsIThreadShutdown** aShutdown) {
 | |
|   LOG(("THRD(%p) begin shutdown\n", this));
 | |
| 
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   MOZ_ASSERT(mThread != PR_GetCurrentThread());
 | |
|   if (NS_WARN_IF(mThread == PR_GetCurrentThread())) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   // Prevent multiple calls to this method.
 | |
|   if (!mShutdownRequired.compareExchange(true, false)) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
|   MOZ_ASSERT(mThread);
 | |
| 
 | |
|   RefPtr<nsThread> currentThread = nsThreadManager::get().GetCurrentThread();
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(currentThread->EventQueue(),
 | |
|                         "Shutdown() may only be called from an XPCOM thread");
 | |
| 
 | |
|   // Allocate a shutdown context, and record that we're waiting for it.
 | |
|   RefPtr<nsThreadShutdownContext> context =
 | |
|       new nsThreadShutdownContext(WrapNotNull(this), currentThread);
 | |
| 
 | |
|   ++currentThread->mOutstandingShutdownContexts;
 | |
|   nsCOMPtr<nsIRunnable> clearOutstanding = NS_NewRunnableFunction(
 | |
|       "nsThread::ClearOutstandingShutdownContext",
 | |
|       [currentThread] { --currentThread->mOutstandingShutdownContexts; });
 | |
|   context->OnCompletion(clearOutstanding);
 | |
| 
 | |
|   // Set mShutdownContext and wake up the thread in case it is waiting for
 | |
|   // events to process.
 | |
|   nsCOMPtr<nsIRunnable> event =
 | |
|       new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context));
 | |
|   if (!mEvents->PutEvent(event.forget(), EventQueuePriority::Normal)) {
 | |
|     // We do not expect this to happen. Let's collect some diagnostics.
 | |
|     nsAutoCString threadName;
 | |
|     GetThreadName(threadName);
 | |
|     MOZ_CRASH_UNSAFE_PRINTF("Attempt to shutdown an already dead thread: %s",
 | |
|                             threadName.get());
 | |
|   }
 | |
| 
 | |
|   // We could still end up with other events being added after the shutdown
 | |
|   // task, but that's okay because we process pending events in ThreadFunc
 | |
|   // after setting mShutdownContext just before exiting.
 | |
|   context.forget(aShutdown);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext) {
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
|   MOZ_ASSERT(aContext->mTerminatingThread == this);
 | |
| 
 | |
| #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|   {
 | |
|     MutexAutoLock lock(aContext->mJoiningThreadMutex);
 | |
| 
 | |
|     // StopWaitingAndLeakThread is explicitely meant to not cause a
 | |
|     // nsThreadShutdownAckEvent on the joining thread, which is the only
 | |
|     // caller of ShutdownComplete.
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!aContext->mThreadLeaked);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   MaybeRemoveFromThreadList();
 | |
| 
 | |
|   // Now, it should be safe to join without fear of dead-locking.
 | |
|   PR_JoinThread(aContext->mTerminatingPRThread);
 | |
|   MOZ_ASSERT(!mThread);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
 | |
|   MOZ_ASSERT(!obs, "Should have been cleared at shutdown!");
 | |
| #endif
 | |
| 
 | |
|   aContext->MarkCompleted();
 | |
| }
 | |
| 
 | |
| void nsThread::WaitForAllAsynchronousShutdowns() {
 | |
|   // This is the motivating example for why SpinEventLoopUntil
 | |
|   // has the template parameter we are providing here.
 | |
|   SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
 | |
|       "nsThread::WaitForAllAsynchronousShutdowns"_ns,
 | |
|       [&]() { return mOutstandingShutdownContexts == 0; }, this);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::Shutdown() {
 | |
|   LOG(("THRD(%p) sync shutdown\n", this));
 | |
| 
 | |
|   nsCOMPtr<nsIThreadShutdown> context;
 | |
|   nsresult rv = BeginShutdown(getter_AddRefs(context));
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return NS_OK;  // The thread has already shut down.
 | |
|   }
 | |
| 
 | |
|   // If we are going to hang here we want to see the thread's name
 | |
|   nsAutoCString threadName;
 | |
|   GetThreadName(threadName);
 | |
| 
 | |
|   // Process events on the current thread until we receive a shutdown ACK.
 | |
|   // Allows waiting; ensure no locks are held that would deadlock us!
 | |
|   SpinEventLoopUntil("nsThread::Shutdown: "_ns + threadName,
 | |
|                      [&]() { return context->GetCompleted(); });
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::HasPendingEvents(bool* aResult) {
 | |
|   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
 | |
|     return NS_ERROR_NOT_SAME_THREAD;
 | |
|   }
 | |
| 
 | |
|   if (mIsMainThread) {
 | |
|     *aResult = TaskController::Get()->HasMainThreadPendingTasks();
 | |
|   } else {
 | |
|     *aResult = mEvents->HasPendingEvent();
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::HasPendingHighPriorityEvents(bool* aResult) {
 | |
|   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
 | |
|     return NS_ERROR_NOT_SAME_THREAD;
 | |
|   }
 | |
| 
 | |
|   // This function appears to never be called anymore.
 | |
|   *aResult = false;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
 | |
|                           EventQueuePriority aQueue) {
 | |
|   nsCOMPtr<nsIRunnable> event = aEvent;
 | |
| 
 | |
|   if (NS_WARN_IF(!event)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   if (!mEvents->PutEvent(event.forget(), aQueue)) {
 | |
|     NS_WARNING(
 | |
|         "An idle event was posted to a thread that will never run it "
 | |
|         "(rejected)");
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP nsThread::SetThreadQoS(nsIThread::QoSPriority aPriority) {
 | |
|   if (!StaticPrefs::threads_use_low_power_enabled()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   // The approach here is to have a thread set itself for its QoS level,
 | |
|   // so we assert if we aren't on the current thread.
 | |
|   MOZ_ASSERT(IsOnCurrentThread(), "Can only change the current thread's QoS");
 | |
| 
 | |
| #if defined(XP_MACOSX)
 | |
|   // Only arm64 macs may possess heterogeneous cores. On these, we can tell
 | |
|   // a thread to set its own QoS status. On intel macs things should behave
 | |
|   // normally, and the OS will ignore the QoS state of the thread.
 | |
|   if (aPriority == nsIThread::QOS_PRIORITY_LOW) {
 | |
|     pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0);
 | |
|   } else if (NS_IsMainThread()) {
 | |
|     // MacOS documentation specifies that a main thread should be initialized at
 | |
|     // the USER_INTERACTIVE priority, so when we restore thread priorities the
 | |
|     // main thread should be setting itself to this.
 | |
|     pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
 | |
|   } else {
 | |
|     pthread_set_qos_class_self_np(QOS_CLASS_DEFAULT, 0);
 | |
|   }
 | |
| #endif
 | |
|   // Do nothing if an OS-specific implementation is unavailable.
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_CANARY
 | |
| void canary_alarm_handler(int signum);
 | |
| 
 | |
| class Canary {
 | |
|   // XXX ToDo: support nested loops
 | |
|  public:
 | |
|   Canary() {
 | |
|     if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) {
 | |
|       signal(SIGALRM, canary_alarm_handler);
 | |
|       ualarm(15000, 0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ~Canary() {
 | |
|     if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) {
 | |
|       ualarm(0, 0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static bool EventLatencyIsImportant() {
 | |
|     return NS_IsMainThread() && XRE_IsParentProcess();
 | |
|   }
 | |
| };
 | |
| 
 | |
| void canary_alarm_handler(int signum) {
 | |
|   void* array[30];
 | |
|   const char msg[29] = "event took too long to run:\n";
 | |
|   // use write to be safe in the signal handler
 | |
|   write(sCanaryOutputFD, msg, sizeof(msg));
 | |
|   backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD);
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #define NOTIFY_EVENT_OBSERVERS(observers_, func_, params_)                 \
 | |
|   do {                                                                     \
 | |
|     if (!observers_.IsEmpty()) {                                           \
 | |
|       for (nsCOMPtr<nsIThreadObserver> obs_ : observers_.ForwardRange()) { \
 | |
|         obs_->func_ params_;                                               \
 | |
|       }                                                                    \
 | |
|     }                                                                      \
 | |
|   } while (0)
 | |
| 
 | |
| size_t nsThread::ShallowSizeOfIncludingThis(
 | |
|     mozilla::MallocSizeOf aMallocSizeOf) const {
 | |
|   size_t n = 0;
 | |
|   if (mShutdownContext) {
 | |
|     n += aMallocSizeOf(mShutdownContext);
 | |
|   }
 | |
|   return aMallocSizeOf(this) + aMallocSizeOf(mThread) + n;
 | |
| }
 | |
| 
 | |
| size_t nsThread::SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const {
 | |
|   size_t n = 0;
 | |
|   if (mEventTarget) {
 | |
|     // The size of mEvents is reported by mEventTarget.
 | |
|     n += mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
 | |
|   }
 | |
|   return n;
 | |
| }
 | |
| 
 | |
| size_t nsThread::SizeOfIncludingThis(
 | |
|     mozilla::MallocSizeOf aMallocSizeOf) const {
 | |
|   return ShallowSizeOfIncludingThis(aMallocSizeOf) +
 | |
|          SizeOfEventQueues(aMallocSizeOf);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) {
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
 | |
|        mNestedEventLoopDepth));
 | |
| 
 | |
|   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
 | |
|     return NS_ERROR_NOT_SAME_THREAD;
 | |
|   }
 | |
| 
 | |
|   // The toplevel event loop normally blocks waiting for the next event, but
 | |
|   // if we're trying to shut this thread down, we must exit the event loop
 | |
|   // when the event queue is empty. This only applys to the toplevel event
 | |
|   // loop! Nested event loops (e.g. during sync dispatch) are waiting for
 | |
|   // some state change and must be able to block even if something has
 | |
|   // requested shutdown of the thread. Otherwise we'll just busywait as we
 | |
|   // endlessly look for an event, fail to find one, and repeat the nested
 | |
|   // event loop since its state change hasn't happened yet.
 | |
|   bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
 | |
| 
 | |
|   Maybe<dom::AutoNoJSAPI> noJSAPI;
 | |
| 
 | |
|   if (mUseHangMonitor && reallyWait) {
 | |
|     BackgroundHangMonitor().NotifyWait();
 | |
|   }
 | |
| 
 | |
|   if (mIsMainThread) {
 | |
|     DoMainThreadSpecificProcessing();
 | |
|   }
 | |
| 
 | |
|   ++mNestedEventLoopDepth;
 | |
| 
 | |
|   // We only want to create an AutoNoJSAPI on threads that actually do DOM
 | |
|   // stuff (including workers).  Those are exactly the threads that have an
 | |
|   // mScriptObserver.
 | |
|   bool callScriptObserver = !!mScriptObserver;
 | |
|   if (callScriptObserver) {
 | |
|     noJSAPI.emplace();
 | |
|     mScriptObserver->BeforeProcessTask(reallyWait);
 | |
|   }
 | |
| 
 | |
|   DrainDirectTasks();
 | |
| 
 | |
| #ifdef EARLY_BETA_OR_EARLIER
 | |
|   // Need to capture mayWaitForWakeup state before OnProcessNextEvent,
 | |
|   // since on the main thread OnProcessNextEvent ends up waiting for the new
 | |
|   // events.
 | |
|   bool mayWaitForWakeup = reallyWait && !mEvents->HasPendingEvent();
 | |
| #endif
 | |
| 
 | |
|   nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserverOnThread();
 | |
|   if (obs) {
 | |
|     obs->OnProcessNextEvent(this, reallyWait);
 | |
|   }
 | |
| 
 | |
|   NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), OnProcessNextEvent,
 | |
|                          (this, reallyWait));
 | |
| 
 | |
|   DrainDirectTasks();
 | |
| 
 | |
| #ifdef MOZ_CANARY
 | |
|   Canary canary;
 | |
| #endif
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   {
 | |
|     // Scope for |event| to make sure that its destructor fires while
 | |
|     // mNestedEventLoopDepth has been incremented, since that destructor can
 | |
|     // also do work.
 | |
|     nsCOMPtr<nsIRunnable> event;
 | |
|     bool usingTaskController = mIsMainThread;
 | |
|     if (usingTaskController) {
 | |
|       event = TaskController::Get()->GetRunnableForMTTask(reallyWait);
 | |
|     } else {
 | |
|       event = mEvents->GetEvent(reallyWait, &mLastEventDelay);
 | |
|     }
 | |
| 
 | |
|     *aResult = (event.get() != nullptr);
 | |
| 
 | |
|     if (event) {
 | |
| #ifdef EARLY_BETA_OR_EARLIER
 | |
|       if (mayWaitForWakeup && mThread) {
 | |
|         ++mWakeupCount;
 | |
|         if (mWakeupCount == kTelemetryWakeupCountLimit) {
 | |
|           TimeStamp now = TimeStamp::Now();
 | |
|           double ms = (now - mLastWakeupCheckTime).ToMilliseconds();
 | |
|           if (ms < 0) {
 | |
|             ms = 0;
 | |
|           }
 | |
|           const char* name = !mNameForWakeupTelemetry.IsEmpty()
 | |
|                                  ? mNameForWakeupTelemetry.get()
 | |
|                                  : PR_GetThreadName(mThread);
 | |
|           if (!name) {
 | |
|             name = mIsMainThread ? "MainThread" : "(nameless thread)";
 | |
|           }
 | |
|           nsDependentCString key(name);
 | |
|           Telemetry::Accumulate(Telemetry::THREAD_WAKEUP, key,
 | |
|                                 static_cast<uint32_t>(ms));
 | |
|           mLastWakeupCheckTime = now;
 | |
|           mWakeupCount = 0;
 | |
|         }
 | |
|       }
 | |
| #endif
 | |
| 
 | |
|       LOG(("THRD(%p) running [%p]\n", this, event.get()));
 | |
| 
 | |
|       Maybe<LogRunnable::Run> log;
 | |
| 
 | |
|       if (!usingTaskController) {
 | |
|         log.emplace(event);
 | |
|       }
 | |
| 
 | |
|       // Delay event processing to encourage whoever dispatched this event
 | |
|       // to run.
 | |
|       DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
 | |
| 
 | |
|       mozilla::TimeStamp now = mozilla::TimeStamp::Now();
 | |
| 
 | |
|       if (mUseHangMonitor) {
 | |
|         BackgroundHangMonitor().NotifyActivity();
 | |
|       }
 | |
| 
 | |
|       Maybe<PerformanceCounterState::Snapshot> snapshot;
 | |
|       if (!usingTaskController) {
 | |
|         snapshot.emplace(mPerformanceCounterState.RunnableWillRun(now, false));
 | |
|       }
 | |
| 
 | |
|       mLastEventStart = now;
 | |
| 
 | |
|       if (!usingTaskController) {
 | |
|         AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
 | |
|         event->Run();
 | |
|       } else {
 | |
|         // Avoid generating "Runnable" profiler markers for the
 | |
|         // "TaskController::ExecutePendingMTTasks" runnables created
 | |
|         // by TaskController, which already adds "Runnable" markers
 | |
|         // when executing tasks.
 | |
|         event->Run();
 | |
|       }
 | |
| 
 | |
|       if (usingTaskController) {
 | |
|         *aResult = TaskController::Get()->MTTaskRunnableProcessedTask();
 | |
|       } else {
 | |
|         mPerformanceCounterState.RunnableDidRun(EmptyCString(),
 | |
|                                                 std::move(snapshot.ref()));
 | |
|       }
 | |
| 
 | |
|       // To cover the event's destructor code inside the LogRunnable span.
 | |
|       event = nullptr;
 | |
|     } else {
 | |
|       mLastEventDelay = TimeDuration();
 | |
|       mLastEventStart = TimeStamp();
 | |
|       if (aMayWait) {
 | |
|         MOZ_ASSERT(ShuttingDown(),
 | |
|                    "This should only happen when shutting down");
 | |
|         rv = NS_ERROR_UNEXPECTED;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DrainDirectTasks();
 | |
| 
 | |
|   NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), AfterProcessNextEvent,
 | |
|                          (this, *aResult));
 | |
| 
 | |
|   if (obs) {
 | |
|     obs->AfterProcessNextEvent(this, *aResult);
 | |
|   }
 | |
| 
 | |
|   // In case some EventObserver dispatched some direct tasks; process them
 | |
|   // now.
 | |
|   DrainDirectTasks();
 | |
| 
 | |
|   if (callScriptObserver) {
 | |
|     if (mScriptObserver) {
 | |
|       mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
 | |
|     }
 | |
|     noJSAPI.reset();
 | |
|   }
 | |
| 
 | |
|   --mNestedEventLoopDepth;
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsISupportsPriority
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::GetPriority(int32_t* aPriority) {
 | |
|   *aPriority = mPriority;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::SetPriority(int32_t aPriority) {
 | |
|   if (NS_WARN_IF(!mThread)) {
 | |
|     return NS_ERROR_NOT_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   // NSPR defines the following four thread priorities:
 | |
|   //   PR_PRIORITY_LOW
 | |
|   //   PR_PRIORITY_NORMAL
 | |
|   //   PR_PRIORITY_HIGH
 | |
|   //   PR_PRIORITY_URGENT
 | |
|   // We map the priority values defined on nsISupportsPriority to these
 | |
|   // values.
 | |
| 
 | |
|   mPriority = aPriority;
 | |
| 
 | |
|   PRThreadPriority pri;
 | |
|   if (mPriority <= PRIORITY_HIGHEST) {
 | |
|     pri = PR_PRIORITY_URGENT;
 | |
|   } else if (mPriority < PRIORITY_NORMAL) {
 | |
|     pri = PR_PRIORITY_HIGH;
 | |
|   } else if (mPriority > PRIORITY_NORMAL) {
 | |
|     pri = PR_PRIORITY_LOW;
 | |
|   } else {
 | |
|     pri = PR_PRIORITY_NORMAL;
 | |
|   }
 | |
|   // If chaos mode is active, retain the randomly chosen priority
 | |
|   if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
 | |
|     PR_SetThreadPriority(mThread, pri);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::AdjustPriority(int32_t aDelta) {
 | |
|   return SetPriority(mPriority + aDelta);
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsIThreadInternal
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::GetObserver(nsIThreadObserver** aObs) {
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
 | |
|   obs.forget(aObs);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::SetObserver(nsIThreadObserver* aObs) {
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
 | |
|     return NS_ERROR_NOT_SAME_THREAD;
 | |
|   }
 | |
| 
 | |
|   mEvents->SetObserver(aObs);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| uint32_t nsThread::RecursionDepth() const {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == mThread);
 | |
|   return mNestedEventLoopDepth;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::AddObserver(nsIThreadObserver* aObserver) {
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   if (NS_WARN_IF(!aObserver)) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
|   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
 | |
|     return NS_ERROR_NOT_SAME_THREAD;
 | |
|   }
 | |
| 
 | |
|   EventQueue()->AddObserver(aObserver);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::RemoveObserver(nsIThreadObserver* aObserver) {
 | |
|   MOZ_ASSERT(mEvents);
 | |
|   NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
 | |
| 
 | |
|   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
 | |
|     return NS_ERROR_NOT_SAME_THREAD;
 | |
|   }
 | |
| 
 | |
|   EventQueue()->RemoveObserver(aObserver);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsThread::SetScriptObserver(
 | |
|     mozilla::CycleCollectedJSContext* aScriptObserver) {
 | |
|   if (!aScriptObserver) {
 | |
|     mScriptObserver = nullptr;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!mScriptObserver);
 | |
|   mScriptObserver = aScriptObserver;
 | |
| }
 | |
| 
 | |
| void NS_DispatchMemoryPressure();
 | |
| 
 | |
| void nsThread::DoMainThreadSpecificProcessing() const {
 | |
|   MOZ_ASSERT(mIsMainThread);
 | |
| 
 | |
|   ipc::CancelCPOWs();
 | |
| 
 | |
|   // Fire a memory pressure notification, if one is pending.
 | |
|   if (!ShuttingDown()) {
 | |
|     NS_DispatchMemoryPressure();
 | |
|   }
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsIDirectTaskDispatcher
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
 | |
|   if (!IsOnCurrentThread()) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   mDirectTasks.AddTask(std::move(aEvent));
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP nsThread::DrainDirectTasks() {
 | |
|   if (!IsOnCurrentThread()) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   mDirectTasks.DrainTasks();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP nsThread::HaveDirectTasks(bool* aValue) {
 | |
|   if (!IsOnCurrentThread()) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   *aValue = mDirectTasks.HaveTasks();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsThreadShutdownContext, nsIThreadShutdown)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadShutdownContext::OnCompletion(nsIRunnable* aEvent) {
 | |
|   if (mCompleted) {
 | |
|     aEvent->Run();
 | |
|   } else {
 | |
|     mCompletionCallbacks.AppendElement(aEvent);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadShutdownContext::GetCompleted(bool* aCompleted) {
 | |
|   *aCompleted = mCompleted;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsThreadShutdownContext::StopWaitingAndLeakThread() {
 | |
|   // Take the joining thread from `mJoiningThread` so that the terminating
 | |
|   // thread won't try to dispatch nsThreadShutdownAckEvent to us anymore.
 | |
|   RefPtr<nsThread> joiningThread;
 | |
|   {
 | |
|     MutexAutoLock lock(mJoiningThreadMutex);
 | |
|     if (!mJoiningThread) {
 | |
|       // Shutdown is already being resolved, so there's nothing for us to do.
 | |
|       return NS_ERROR_NOT_AVAILABLE;
 | |
|     }
 | |
|     joiningThread = mJoiningThread.forget();
 | |
|     mThreadLeaked = true;
 | |
|   }
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(joiningThread->IsOnCurrentThread());
 | |
| 
 | |
|   MarkCompleted();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsThreadShutdownContext::MarkCompleted() {
 | |
|   MOZ_ASSERT(!mCompleted);
 | |
|   mCompleted = true;
 | |
|   nsTArray<nsCOMPtr<nsIRunnable>> callbacks(std::move(mCompletionCallbacks));
 | |
|   for (auto& callback : callbacks) {
 | |
|     callback->Run();
 | |
|   }
 | |
| }
 | |
| 
 | |
| namespace mozilla {
 | |
| PerformanceCounterState::Snapshot PerformanceCounterState::RunnableWillRun(
 | |
|     TimeStamp aNow, bool aIsIdleRunnable) {
 | |
|   if (mIsMainThread && IsNestedRunnable()) {
 | |
|     // Flush out any accumulated time that should be accounted to the
 | |
|     // current runnable before we start running a nested runnable.  Don't
 | |
|     // do this for non-mainthread threads that may be running their own
 | |
|     // event loops, like SocketThread.
 | |
|     MaybeReportAccumulatedTime("nested runnable"_ns, aNow);
 | |
|   }
 | |
| 
 | |
|   Snapshot snapshot(mCurrentEventLoopDepth, mCurrentRunnableIsIdleRunnable);
 | |
| 
 | |
|   mCurrentEventLoopDepth = mNestedEventLoopDepth;
 | |
|   mCurrentRunnableIsIdleRunnable = aIsIdleRunnable;
 | |
|   mCurrentTimeSliceStart = aNow;
 | |
| 
 | |
|   return snapshot;
 | |
| }
 | |
| 
 | |
| void PerformanceCounterState::RunnableDidRun(const nsCString& aName,
 | |
|                                              Snapshot&& aSnapshot) {
 | |
|   // First thing: Restore our mCurrentEventLoopDepth so we can use
 | |
|   // IsNestedRunnable().
 | |
|   mCurrentEventLoopDepth = aSnapshot.mOldEventLoopDepth;
 | |
| 
 | |
|   // We may not need the current timestamp; don't bother computing it if we
 | |
|   // don't.
 | |
|   TimeStamp now;
 | |
|   if (mLongTaskLength.isSome() || IsNestedRunnable()) {
 | |
|     now = TimeStamp::Now();
 | |
|   }
 | |
|   if (mLongTaskLength.isSome()) {
 | |
|     MaybeReportAccumulatedTime(aName, now);
 | |
|   }
 | |
| 
 | |
|   // And now restore the rest of our state.
 | |
|   mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable;
 | |
|   if (IsNestedRunnable()) {
 | |
|     // Reset mCurrentTimeSliceStart to right now, so our parent runnable's
 | |
|     // next slice can be properly accounted for.
 | |
|     mCurrentTimeSliceStart = now;
 | |
|   } else {
 | |
|     // We are done at the outermost level; we are no longer in a timeslice.
 | |
|     mCurrentTimeSliceStart = TimeStamp();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void PerformanceCounterState::MaybeReportAccumulatedTime(const nsCString& aName,
 | |
|                                                          TimeStamp aNow) {
 | |
|   MOZ_ASSERT(mCurrentTimeSliceStart,
 | |
|              "How did we get here if we're not in a timeslice?");
 | |
|   if (!mLongTaskLength.isSome()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   TimeDuration duration = aNow - mCurrentTimeSliceStart;
 | |
| #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
 | |
|   if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_TELEMETRY_MS) {
 | |
|     Telemetry::Accumulate(Telemetry::EVENT_LONGTASK, aName,
 | |
|                           duration.ToMilliseconds());
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // Long tasks only matter on the main thread.
 | |
|   if (duration.ToMilliseconds() >= mLongTaskLength.value()) {
 | |
|     // Idle events (gc...) don't *really* count here
 | |
|     if (!mCurrentRunnableIsIdleRunnable) {
 | |
|       mLastLongNonIdleTaskEnd = aNow;
 | |
|     }
 | |
|     mLastLongTaskEnd = aNow;
 | |
| 
 | |
|     if (profiler_thread_is_being_profiled_for_markers()) {
 | |
|       struct LongTaskMarker {
 | |
|         static constexpr Span<const char> MarkerTypeName() {
 | |
|           return MakeStringSpan("MainThreadLongTask");
 | |
|         }
 | |
|         static void StreamJSONMarkerData(
 | |
|             baseprofiler::SpliceableJSONWriter& aWriter) {
 | |
|           aWriter.StringProperty("category", "LongTask");
 | |
|         }
 | |
|         static MarkerSchema MarkerTypeDisplay() {
 | |
|           using MS = MarkerSchema;
 | |
|           MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
 | |
|           schema.AddKeyLabelFormatSearchable("category", "Type",
 | |
|                                              MS::Format::String,
 | |
|                                              MS::Searchable::Searchable);
 | |
|           return schema;
 | |
|         }
 | |
|       };
 | |
| 
 | |
|       profiler_add_marker(mCurrentRunnableIsIdleRunnable
 | |
|                               ? ProfilerString8View("LongIdleTask")
 | |
|                               : ProfilerString8View("LongTask"),
 | |
|                           geckoprofiler::category::OTHER,
 | |
|                           MarkerTiming::Interval(mCurrentTimeSliceStart, aNow),
 | |
|                           LongTaskMarker{});
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | 
