diff options
Diffstat (limited to '')
-rw-r--r-- | xpcom/threads/nsThread.h | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h new file mode 100644 index 0000000000..e4b0eece51 --- /dev/null +++ b/xpcom/threads/nsThread.h @@ -0,0 +1,400 @@ +/* -*- 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/. */ + +#ifndef nsThread_h__ +#define nsThread_h__ + +#include "MainThreadUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" +#include "mozilla/EventQueue.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/NotNull.h" +#include "mozilla/PerformanceCounter.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsIEventTarget.h" +#include "nsISerialEventTarget.h" +#include "nsISupportsPriority.h" +#include "nsIThread.h" +#include "nsIThreadInternal.h" +#include "nsTArray.h" + +namespace mozilla { +class CycleCollectedJSContext; +class DelayedRunnable; +class SynchronizedEventQueue; +class ThreadEventQueue; +class ThreadEventTarget; + +template <typename T, size_t Length> +class Array; +} // namespace mozilla + +using mozilla::NotNull; + +class nsIRunnable; +class nsThreadEnumerator; +class nsThreadShutdownContext; + +// See https://www.w3.org/TR/longtasks +#define LONGTASK_BUSY_WINDOW_MS 50 + +// Time a Runnable executes before we accumulate telemetry on it +#define LONGTASK_TELEMETRY_MS 30 + +// A class for managing performance counter state. +namespace mozilla { +class PerformanceCounterState { + public: + explicit PerformanceCounterState(const uint32_t& aNestedEventLoopDepthRef, + bool aIsMainThread) + : mNestedEventLoopDepth(aNestedEventLoopDepthRef), + mIsMainThread(aIsMainThread), + // Does it really make sense to initialize these to "now" when we + // haven't run any tasks? + mLastLongTaskEnd(TimeStamp::Now()), + mLastLongNonIdleTaskEnd(mLastLongTaskEnd) {} + + class Snapshot { + public: + Snapshot(uint32_t aOldEventLoopDepth, PerformanceCounter* aCounter, + bool aOldIsIdleRunnable) + : mOldEventLoopDepth(aOldEventLoopDepth), + mOldPerformanceCounter(aCounter), + mOldIsIdleRunnable(aOldIsIdleRunnable) {} + + Snapshot(const Snapshot&) = default; + Snapshot(Snapshot&&) = default; + + private: + friend class PerformanceCounterState; + + const uint32_t mOldEventLoopDepth; + // Non-const so we can move out of it and avoid the extra refcounting. + RefPtr<PerformanceCounter> mOldPerformanceCounter; + const bool mOldIsIdleRunnable; + }; + + // Notification that a runnable is about to run. This captures a snapshot of + // our current state before we reset to prepare for the new runnable. This + // muast be called after mNestedEventLoopDepth has been incremented for the + // runnable execution. The performance counter passed in should be the one + // for the relevant runnable and may be null. aIsIdleRunnable should be true + // if and only if the runnable has idle priority. + Snapshot RunnableWillRun(PerformanceCounter* Counter, TimeStamp aNow, + bool aIsIdleRunnable); + + // Notification that a runnable finished executing. This must be passed the + // snapshot that RunnableWillRun returned for the same runnable. This must be + // called before mNestedEventLoopDepth is decremented after the runnable's + // execution. + void RunnableDidRun(const nsCString& aName, Snapshot&& aSnapshot); + + const TimeStamp& LastLongTaskEnd() const { return mLastLongTaskEnd; } + const TimeStamp& LastLongNonIdleTaskEnd() const { + return mLastLongNonIdleTaskEnd; + } + + private: + // Called to report accumulated time, as needed, when we're about to run a + // runnable or just finished running one. + void MaybeReportAccumulatedTime(const nsCString& aName, TimeStamp aNow); + + // Whether the runnable we are about to run, or just ran, is a nested + // runnable, in the sense that there is some other runnable up the stack + // spinning the event loop. This must be called before we change our + // mCurrentEventLoopDepth (when about to run a new event) or after we restore + // it (after we ran one). + bool IsNestedRunnable() const { + return mNestedEventLoopDepth > mCurrentEventLoopDepth; + } + + // The event loop depth of the currently running runnable. Set to the max + // value of a uint32_t when there is no runnable running, so when starting to + // run a toplevel (not nested) runnable IsNestedRunnable() will test false. + uint32_t mCurrentEventLoopDepth = std::numeric_limits<uint32_t>::max(); + + // A reference to the nsThread's mNestedEventLoopDepth, so we can + // see what it is right now. + const uint32_t& mNestedEventLoopDepth; + + // A boolean that indicates whether the currently running runnable is an idle + // runnable. Only has a useful value between RunnableWillRun() being called + // and RunnableDidRun() returning. + bool mCurrentRunnableIsIdleRunnable = false; + + // Whether we're attached to the mainthread nsThread. + const bool mIsMainThread; + + // The timestamp from which time to be accounted for should be measured. This + // can be the start of a runnable running or the end of a nested runnable + // running. + TimeStamp mCurrentTimeSliceStart; + + // Information about when long tasks last ended. + TimeStamp mLastLongTaskEnd; + TimeStamp mLastLongNonIdleTaskEnd; + + // The performance counter to use for accumulating the runtime of + // the currently running event. May be null, in which case the + // event's running time should not be accounted to any performance + // counters. + RefPtr<PerformanceCounter> mCurrentPerformanceCounter; +}; +} // namespace mozilla + +// A native thread +class nsThread : public nsIThreadInternal, + public nsISupportsPriority, + public nsIDirectTaskDispatcher, + private mozilla::LinkedListElement<nsThread> { + friend mozilla::LinkedList<nsThread>; + friend mozilla::LinkedListElement<nsThread>; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREAD + NS_DECL_NSITHREADINTERNAL + NS_DECL_NSISUPPORTSPRIORITY + NS_DECL_NSIDIRECTTASKDISPATCHER + + enum MainThreadFlag { MAIN_THREAD, NOT_MAIN_THREAD }; + + nsThread(NotNull<mozilla::SynchronizedEventQueue*> aQueue, + MainThreadFlag aMainThread, + nsIThreadManager::ThreadCreationOptions aOptions); + + private: + nsThread(); + + public: + // Initialize this as a named wrapper for a new PRThread. + nsresult Init(const nsACString& aName); + + // Initialize this as a wrapper for the current PRThread. + nsresult InitCurrentThread(); + + // Get this thread's name, thread-safe. + void GetThreadName(nsACString& aNameBuffer); + + // Set this thread's name. Consider using + // NS_SetCurrentThreadName if you are not sure. + void SetThreadNameInternal(const nsACString& aName); + + private: + // Initializes the mThreadId and stack base/size members, and adds the thread + // to the ThreadList(). + void InitCommon(); + + public: + // The PRThread corresponding to this thread. + PRThread* GetPRThread() const { return mThread; } + + const void* StackBase() const { return mStackBase; } + size_t StackSize() const { return mStackSize; } + + uint32_t ThreadId() const { return mThreadId; } + + // If this flag is true, then the nsThread was created using + // nsIThreadManager::NewThread. + bool ShutdownRequired() { return mShutdownRequired; } + + // Lets GetRunningEventDelay() determine if the pool this is part + // of has an unstarted thread + void SetPoolThreadFreePtr(mozilla::Atomic<bool, mozilla::Relaxed>* aPtr) { + mIsAPoolThreadFree = aPtr; + } + + void SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver); + + uint32_t RecursionDepth() const; + + void ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext); + + void WaitForAllAsynchronousShutdowns(); + + static const uint32_t kRunnableNameBufSize = 1000; + static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName; + + mozilla::SynchronizedEventQueue* EventQueue() { return mEvents.get(); } + + bool ShuttingDown() const { return mShutdownContext != nullptr; } + + static bool GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName, + mozilla::EventQueuePriority aPriority); + + virtual mozilla::PerformanceCounter* GetPerformanceCounter( + nsIRunnable* aEvent) const; + + static mozilla::PerformanceCounter* GetPerformanceCounterBase( + nsIRunnable* aEvent); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Returns the size of this object, its PRThread, and its shutdown contexts, + // but excluding its event queues. + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + size_t SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const; + + static nsThreadEnumerator Enumerate(); + + void SetUseHangMonitor(bool aValue) { + MOZ_ASSERT(IsOnCurrentThread()); + mUseHangMonitor = aValue; + } + + private: + void DoMainThreadSpecificProcessing() const; + + protected: + friend class nsThreadShutdownEvent; + + friend class nsThreadEnumerator; + + virtual ~nsThread(); + + static void ThreadFunc(void* aArg); + + // Helper + already_AddRefed<nsIThreadObserver> GetObserver() { + nsIThreadObserver* obs; + nsThread::GetObserver(&obs); + return already_AddRefed<nsIThreadObserver>(obs); + } + + already_AddRefed<nsThreadShutdownContext> ShutdownInternal(bool aSync); + + friend class nsThreadManager; + friend class nsThreadPool; + + static mozilla::OffTheBooksMutex& ThreadListMutex(); + static mozilla::LinkedList<nsThread>& ThreadList(); + + void AddToThreadList(); + void MaybeRemoveFromThreadList(); + + // Whether or not these members have a value determines whether the nsThread + // is treated as a full XPCOM thread or as a thin wrapper. + // + // For full nsThreads, they will always contain valid pointers. For thin + // wrappers around non-XPCOM threads, they will be null, and event dispatch + // methods which rely on them will fail (and assert) if called. + RefPtr<mozilla::SynchronizedEventQueue> mEvents; + RefPtr<mozilla::ThreadEventTarget> mEventTarget; + + // The number of outstanding nsThreadShutdownContext started by this thread. + // The thread will not be allowed to exit until this number reaches 0. + uint32_t mOutstandingShutdownContexts; + // The shutdown context for ourselves. + RefPtr<nsThreadShutdownContext> mShutdownContext; + + mozilla::CycleCollectedJSContext* mScriptObserver; + + // Our name. + mozilla::DataMutex<nsCString> mThreadName; + + void* mStackBase = nullptr; + uint32_t mStackSize; + uint32_t mThreadId; + + uint32_t mNestedEventLoopDepth; + + mozilla::Atomic<bool> mShutdownRequired; + + int8_t mPriority; + + const bool mIsMainThread; + bool mUseHangMonitor; + const bool mIsUiThread; + mozilla::Atomic<bool, mozilla::Relaxed>* mIsAPoolThreadFree; + + // Set to true if this thread creates a JSRuntime. + bool mCanInvokeJS; + + // The time the currently running event spent in event queues, and + // when it started running. If no event is running, they are + // TimeDuration() & TimeStamp(). + mozilla::TimeDuration mLastEventDelay; + mozilla::TimeStamp mLastEventStart; + +#ifdef EARLY_BETA_OR_EARLIER + nsCString mNameForWakeupTelemetry; + mozilla::TimeStamp mLastWakeupCheckTime; + uint32_t mWakeupCount = 0; +#endif + + mozilla::PerformanceCounterState mPerformanceCounterState; + + mozilla::SimpleTaskQueue mDirectTasks; +}; + +class nsThreadShutdownContext final : public nsIThreadShutdown { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITHREADSHUTDOWN + + private: + friend class nsThread; + friend class nsThreadShutdownEvent; + friend class nsThreadShutdownAckEvent; + + nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread, + nsThread* aJoiningThread) + : mTerminatingThread(aTerminatingThread), + mTerminatingPRThread(aTerminatingThread->GetPRThread()), + mJoiningThreadMutex("nsThreadShutdownContext::mJoiningThreadMutex"), + mJoiningThread(aJoiningThread) {} + + ~nsThreadShutdownContext() = default; + + // Must be called on the joining thread. + void MarkCompleted(); + + // NB: This may be the last reference. + NotNull<RefPtr<nsThread>> const mTerminatingThread; + PRThread* const mTerminatingPRThread; + + // May only be accessed on the joining thread. + bool mCompleted = false; + nsTArray<nsCOMPtr<nsIRunnable>> mCompletionCallbacks; + + // The thread waiting for this thread to shut down. Will either be cleared by + // the joining thread if `StopWaitingAndLeakThread` is called or by the + // terminating thread upon exiting and notifying the joining thread. + mozilla::Mutex mJoiningThreadMutex; + RefPtr<nsThread> mJoiningThread MOZ_GUARDED_BY(mJoiningThreadMutex); + bool mThreadLeaked MOZ_GUARDED_BY(mJoiningThreadMutex) = false; +}; + +class MOZ_STACK_CLASS nsThreadEnumerator final { + public: + nsThreadEnumerator() = default; + + auto begin() { return nsThread::ThreadList().begin(); } + auto end() { return nsThread::ThreadList().end(); } + + private: + mozilla::OffTheBooksMutexAutoLock mMal{nsThread::ThreadListMutex()}; +}; + +#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM && \ + defined(_GNU_SOURCE) +# define MOZ_CANARY + +extern int sCanaryOutputFD; +#endif + +#endif // nsThread_h__ |