diff options
Diffstat (limited to 'xpcom/threads/nsThread.h')
-rw-r--r-- | xpcom/threads/nsThread.h | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h new file mode 100644 index 0000000000..305f092dba --- /dev/null +++ b/xpcom/threads/nsThread.h @@ -0,0 +1,444 @@ +/* -*- 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/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 SynchronizedEventQueue; +class ThreadEventQueue; +class ThreadEventTarget; + +template <typename T, size_t Length> +class Array; +} // namespace mozilla + +using mozilla::NotNull; + +class nsIRunnable; +class nsLocalExecutionRecord; +class nsThreadEnumerator; + +// See https://www.w3.org/TR/longtasks +#define LONGTASK_BUSY_WINDOW_MS 50 + +// 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(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(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, uint32_t aStackSize); + + 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(); + + 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<struct 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(); + + static uint32_t MaxActiveThreads(); + + // When entering local execution mode a new event queue is created and used as + // an event source. This queue is only accessible through an + // nsLocalExecutionGuard constructed from the nsLocalExecutionRecord returned + // by this function, effectively restricting the events that get run while in + // local execution mode to those dispatched by the owner of the guard object. + // + // Local execution is not nestable. When the nsLocalExecutionGuard is + // destructed, the thread exits the local execution mode. + // + // Note that code run in local execution mode is not considered a task in the + // spec sense. Events from the local queue are considered part of the + // enclosing task and as such do not trigger profiling hooks, observer + // notifications, etc. + nsLocalExecutionRecord EnterLocalExecution(); + + 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); + } + + struct nsThreadShutdownContext* ShutdownInternal(bool aSync); + + friend class nsThreadManager; + friend class nsThreadPool; + + static mozilla::OffTheBooksMutex& ThreadListMutex(); + static mozilla::LinkedList<nsThread>& ThreadList(); + static void ClearThreadList(); + + // The current number of active threads. + static uint32_t sActiveThreads; + // The maximum current number of active threads we've had in this session. + static uint32_t sMaxActiveThreads; + + 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 shutdown contexts for any other threads we've asked to shut down. + using ShutdownContexts = + nsTArray<mozilla::UniquePtr<struct nsThreadShutdownContext>>; + + // Helper for finding a ShutdownContext in the contexts array. + struct ShutdownContextsComp { + bool Equals(const ShutdownContexts::elem_type& a, + const ShutdownContexts::elem_type::Pointer b) const; + }; + + ShutdownContexts mRequestedShutdownContexts; + // The shutdown context for ourselves. + struct nsThreadShutdownContext* mShutdownContext; + + mozilla::CycleCollectedJSContext* mScriptObserver; + + void* mStackBase = nullptr; + uint32_t mStackSize; + uint32_t mThreadId; + + uint32_t mNestedEventLoopDepth; + + mozilla::Atomic<bool> mShutdownRequired; + + int8_t mPriority; + + const bool mIsMainThread; + bool mUseHangMonitor; + mozilla::Atomic<bool, mozilla::Relaxed>* mIsAPoolThreadFree; + + // Set to true if this thread creates a JSRuntime. + bool mCanInvokeJS; + + bool mHasTLSEntry = false; + + // 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; + + bool mIsInLocalExecutionMode = false; + + mozilla::SimpleTaskQueue mDirectTasks; +}; + +struct nsThreadShutdownContext { + nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread, + NotNull<nsThread*> aJoiningThread, + bool aAwaitingShutdownAck) + : mTerminatingThread(aTerminatingThread), + mTerminatingPRThread(aTerminatingThread->GetPRThread()), + mJoiningThread(aJoiningThread), + mAwaitingShutdownAck(aAwaitingShutdownAck), + mIsMainThreadJoining(NS_IsMainThread()) { + MOZ_COUNT_CTOR(nsThreadShutdownContext); + } + MOZ_COUNTED_DTOR(nsThreadShutdownContext) + + // NB: This will be the last reference. + NotNull<RefPtr<nsThread>> mTerminatingThread; + PRThread* const mTerminatingPRThread; + NotNull<nsThread*> MOZ_UNSAFE_REF( + "Thread manager is holding reference to joining thread") mJoiningThread; + bool mAwaitingShutdownAck; + bool mIsMainThreadJoining; +}; + +// This RAII class controls the duration of the associated nsThread's local +// execution mode and provides access to the local event target. (See +// nsThread::EnterLocalExecution() for details.) It is constructed from an +// nsLocalExecutionRecord, which can only be constructed by nsThread. +class MOZ_RAII nsLocalExecutionGuard final { + public: + MOZ_IMPLICIT nsLocalExecutionGuard( + nsLocalExecutionRecord&& aLocalExecutionRecord); + nsLocalExecutionGuard(const nsLocalExecutionGuard&) = delete; + nsLocalExecutionGuard(nsLocalExecutionGuard&&) = delete; + ~nsLocalExecutionGuard(); + + nsCOMPtr<nsISerialEventTarget> GetEventTarget() const { + return mLocalEventTarget; + } + + private: + mozilla::SynchronizedEventQueue& mEventQueueStack; + nsCOMPtr<nsISerialEventTarget> mLocalEventTarget; + bool& mLocalExecutionFlag; +}; + +class MOZ_TEMPORARY_CLASS nsLocalExecutionRecord final { + private: + friend class nsThread; + friend class nsLocalExecutionGuard; + + nsLocalExecutionRecord(mozilla::SynchronizedEventQueue& aEventQueueStack, + bool& aLocalExecutionFlag) + : mEventQueueStack(aEventQueueStack), + mLocalExecutionFlag(aLocalExecutionFlag) {} + + nsLocalExecutionRecord(nsLocalExecutionRecord&&) = default; + + public: + nsLocalExecutionRecord(const nsLocalExecutionRecord&) = delete; + + private: + mozilla::SynchronizedEventQueue& mEventQueueStack; + bool& mLocalExecutionFlag; +}; + +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__ |